aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--Gemfile10
-rw-r--r--README.md2
-rw-r--r--actionmailer/lib/action_mailer/base.rb4
-rw-r--r--actionmailer/test/base_test.rb259
-rw-r--r--actionmailer/test/delivery_methods_test.rb72
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb25
-rw-r--r--actionmailer/test/log_subscriber_test.rb8
-rw-r--r--actionmailer/test/mail_layout_test.rb10
-rw-r--r--actionpack/CHANGELOG.md102
-rw-r--r--actionpack/RUNNING_UNIT_TESTS.rdoc4
-rw-r--r--actionpack/lib/abstract_controller/base.rb38
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb64
-rw-r--r--actionpack/lib/abstract_controller/url_for.rb2
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb8
-rw-r--r--actionpack/lib/action_controller/metal.rb7
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb16
-rw-r--r--actionpack/lib/action_controller/metal/live.rb3
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb12
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb20
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb2
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb4
-rw-r--r--actionpack/lib/action_controller/test_case.rb37
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb27
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb77
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb88
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y5
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb6
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb68
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb131
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb (renamed from actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb)0
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb182
-rw-r--r--actionpack/lib/action_dispatch/routing.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb92
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb241
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb90
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb6
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb6
-rw-r--r--actionpack/test/abstract/collector_test.rb16
-rw-r--r--actionpack/test/abstract_unit.rb4
-rw-r--r--actionpack/test/controller/assert_select_test.rb24
-rw-r--r--actionpack/test/controller/caching_test.rb16
-rw-r--r--actionpack/test/controller/flash_test.rb23
-rw-r--r--actionpack/test/controller/force_ssl_test.rb15
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb7
-rw-r--r--actionpack/test/controller/integration_test.rb37
-rw-r--r--actionpack/test/controller/live_stream_test.rb16
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb2
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb7
-rw-r--r--actionpack/test/controller/mime/respond_with_test.rb35
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb17
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb43
-rw-r--r--actionpack/test/controller/routing_test.rb9
-rw-r--r--actionpack/test/controller/send_file_test.rb3
-rw-r--r--actionpack/test/controller/url_for_test.rb32
-rw-r--r--actionpack/test/dispatch/cookies_test.rb117
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb3
-rw-r--r--actionpack/test/dispatch/header_test.rb2
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb3
-rw-r--r--actionpack/test/dispatch/request_test.rb5
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb4
-rw-r--r--actionpack/test/dispatch/routing_test.rb64
-rw-r--r--actionpack/test/dispatch/ssl_test.rb7
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb6
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb12
-rw-r--r--actionpack/test/journey/router/utils_test.rb8
-rw-r--r--actionpack/test/journey/router_test.rb50
-rw-r--r--actionview/CHANGELOG.md73
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb29
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb16
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb7
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb120
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb17
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb4
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb9
-rw-r--r--actionview/lib/action_view/routing_url_for.rb16
-rw-r--r--actionview/lib/action_view/template/resolver.rb30
-rw-r--r--actionview/lib/action_view/test_case.rb3
-rw-r--r--actionview/lib/action_view/view_paths.rb41
-rw-r--r--actionview/test/actionpack/abstract/abstract_controller_test.rb48
-rw-r--r--actionview/test/actionpack/abstract/render_test.rb16
-rw-r--r--actionview/test/actionpack/controller/render_test.rb6
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb9
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb218
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb29
-rw-r--r--actionview/test/template/form_collections_helper_test.rb51
-rw-r--r--actionview/test/template/form_helper_test.rb25
-rw-r--r--actionview/test/template/form_tag_helper_test.rb13
-rw-r--r--actionview/test/template/sanitize_helper_test.rb2
-rw-r--r--actionview/test/template/test_test.rb18
-rw-r--r--actionview/test/template/translation_helper_test.rb6
-rw-r--r--actionview/test/template/url_helper_test.rb40
-rw-r--r--activemodel/CHANGELOG.md7
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/locale/en.yml12
-rw-r--r--activemodel/lib/active_model/secure_password.rb5
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb18
-rw-r--r--activerecord/CHANGELOG.md357
-rw-r--r--activerecord/README.rdoc18
-rw-r--r--activerecord/Rakefile191
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record/aggregations.rb8
-rw-r--r--activerecord/lib/active_record/association_relation.rb4
-rw-r--r--activerecord/lib/active_record/associations.rb20
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb49
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb18
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb31
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb10
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb36
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb28
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb39
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb9
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb19
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb16
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb34
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb17
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb6
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb16
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb21
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb4
-rw-r--r--activerecord/lib/active_record/autosave_association.rb23
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb87
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb84
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb239
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb184
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb41
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb56
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb62
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb318
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb169
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb55
-rw-r--r--activerecord/lib/active_record/connection_adapters/type.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/binary.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/boolean.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/date.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/date_time.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/decimal.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/float.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/integer.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/numeric.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/string.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/text.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/time.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/time_value.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/type_map.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/type/value.rb43
-rw-r--r--activerecord/lib/active_record/connection_handling.rb35
-rw-r--r--activerecord/lib/active_record/core.rb67
-rw-r--r--activerecord/lib/active_record/counter_cache.rb64
-rw-r--r--activerecord/lib/active_record/enum.rb23
-rw-r--r--activerecord/lib/active_record/fixtures.rb33
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb13
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb10
-rw-r--r--activerecord/lib/active_record/null_relation.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb8
-rw-r--r--activerecord/lib/active_record/railties/databases.rake5
-rw-r--r--activerecord/lib/active_record/reflection.rb47
-rw-r--r--activerecord/lib/active_record/relation.rb52
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb22
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb48
-rw-r--r--activerecord/lib/active_record/relation/merger.rb13
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb15
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb77
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb3
-rw-r--r--activerecord/lib/active_record/sanitization.rb9
-rw-r--r--activerecord/lib/active_record/statement_cache.rb84
-rw-r--r--activerecord/lib/active_record/store.rb19
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb13
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb1
-rw-r--r--activerecord/test/cases/adapter_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql/quoting_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb38
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb47
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb100
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb111
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb65
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb56
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb15
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb14
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb20
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb5
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb47
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_test.rb59
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb41
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb42
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb31
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb10
-rw-r--r--activerecord/test/cases/autosave_association_test.rb13
-rw-r--r--activerecord/test/cases/base_test.rb80
-rw-r--r--activerecord/test/cases/binary_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb14
-rw-r--r--activerecord/test/cases/column_definition_test.rb44
-rw-r--r--activerecord/test/cases/column_test.rb74
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb56
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb54
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb174
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb192
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb61
-rw-r--r--activerecord/test/cases/connection_adapters/type/type_map_test.rb115
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb97
-rw-r--r--activerecord/test/cases/counter_cache_test.rb16
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/dup_test.rb9
-rw-r--r--activerecord/test/cases/enum_test.rb36
-rw-r--r--activerecord/test/cases/explain_test.rb8
-rw-r--r--activerecord/test/cases/fixtures_test.rb9
-rw-r--r--activerecord/test/cases/helper.rb9
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb8
-rw-r--r--activerecord/test/cases/locking_test.rb4
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb7
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb31
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb6
-rw-r--r--activerecord/test/cases/migration/columns_test.rb10
-rw-r--r--activerecord/test/cases/migration/index_test.rb94
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb8
-rw-r--r--activerecord/test/cases/migrator_test.rb2
-rw-r--r--activerecord/test/cases/modules_test.rb28
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb4
-rw-r--r--activerecord/test/cases/persistence_test.rb34
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb26
-rw-r--r--activerecord/test/cases/reflection_test.rb10
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb2
-rw-r--r--activerecord/test/cases/relation/merging_test.rb43
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb21
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb85
-rw-r--r--activerecord/test/cases/relation_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb92
-rw-r--r--activerecord/test/cases/sanitize_test.rb27
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb2
-rw-r--r--activerecord/test/cases/statement_cache_test.rb64
-rw-r--r--activerecord/test/cases/store_test.rb16
-rw-r--r--activerecord/test/cases/test_case.rb8
-rw-r--r--activerecord/test/cases/timestamp_test.rb21
-rw-r--r--activerecord/test/cases/transactions_test.rb13
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb43
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb47
-rw-r--r--activerecord/test/cases/validations_test.rb18
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb12
-rw-r--r--activerecord/test/config.example.yml30
-rw-r--r--activerecord/test/fixtures/uuid_children.yml3
-rw-r--r--activerecord/test/fixtures/uuid_parents.yml2
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/club.rb2
-rw-r--r--activerecord/test/models/comment.rb15
-rw-r--r--activerecord/test/models/company_in_module.rb18
-rw-r--r--activerecord/test/models/developer.rb8
-rw-r--r--activerecord/test/models/pirate.rb7
-rw-r--r--activerecord/test/models/post.rb19
-rw-r--r--activerecord/test/models/publisher.rb2
-rw-r--r--activerecord/test/models/publisher/article.rb3
-rw-r--r--activerecord/test/models/publisher/magazine.rb3
-rw-r--r--activerecord/test/models/reader.rb2
-rw-r--r--activerecord/test/models/ship.rb6
-rw-r--r--activerecord/test/models/tag.rb4
-rw-r--r--activerecord/test/models/uuid_child.rb3
-rw-r--r--activerecord/test/models/uuid_parent.rb3
-rw-r--r--activerecord/test/schema/schema.rb29
-rw-r--r--activesupport/CHANGELOG.md99
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb6
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb30
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb54
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb47
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/duration.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb48
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb4
-rw-r--r--activesupport/lib/active_support/subscriber.rb11
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb7
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb146
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb37
-rw-r--r--activesupport/test/core_ext/duration_test.rb7
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb42
-rw-r--r--activesupport/test/core_ext/kernel_test.rb2
-rw-r--r--activesupport/test/core_ext/module_test.rb2
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb (renamed from activesupport/test/core_ext/deep_dup_test.rb)0
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb (renamed from activesupport/test/core_ext/duplicable_test.rb)25
-rw-r--r--activesupport/test/core_ext/object/json_test.rb9
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb8
-rw-r--r--activesupport/test/core_ext/securerandom_test.rb28
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb17
-rw-r--r--activesupport/test/inflector_test.rb1
-rw-r--r--activesupport/test/inflector_test_cases.rb8
-rw-r--r--activesupport/test/multibyte_conformance_test.rb (renamed from activesupport/test/multibyte_conformance.rb)4
-rw-r--r--activesupport/test/number_helper_test.rb2
-rw-r--r--activesupport/test/subscriber_test.rb25
-rw-r--r--activesupport/test/time_zone_test.rb9
-rw-r--r--activesupport/test/xml_mini_test.rb59
-rwxr-xr-xci/travis.rb2
-rw-r--r--guides/CHANGELOG.md4
-rw-r--r--guides/assets/stylesheets/main.css5
-rw-r--r--guides/code/getting_started/Gemfile6
-rw-r--r--guides/code/getting_started/Rakefile2
-rw-r--r--guides/code/getting_started/config.ru2
-rw-r--r--guides/code/getting_started/config/environment.rb2
-rw-r--r--guides/code/getting_started/config/environments/development.rb10
-rw-r--r--guides/code/getting_started/config/environments/production.rb2
-rw-r--r--guides/code/getting_started/config/environments/test.rb2
-rw-r--r--guides/code/getting_started/config/initializers/secret_token.rb2
-rw-r--r--guides/code/getting_started/config/initializers/session_store.rb2
-rw-r--r--guides/code/getting_started/config/routes.rb2
-rw-r--r--guides/rails_guides.rb4
-rw-r--r--guides/rails_guides/helpers.rb2
-rw-r--r--guides/rails_guides/markdown.rb10
-rw-r--r--guides/source/2_3_release_notes.md4
-rw-r--r--guides/source/3_0_release_notes.md4
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/_license.html.erb2
-rw-r--r--guides/source/_welcome.html.erb7
-rw-r--r--guides/source/action_controller_overview.md10
-rw-r--r--guides/source/action_mailer_basics.md6
-rw-r--r--guides/source/action_view_overview.md2
-rw-r--r--guides/source/active_record_basics.md4
-rw-r--r--guides/source/active_record_callbacks.md3
-rw-r--r--guides/source/active_record_postgresql.md433
-rw-r--r--guides/source/active_record_validations.md2
-rw-r--r--guides/source/active_support_core_extensions.md65
-rw-r--r--guides/source/active_support_instrumentation.md3
-rw-r--r--guides/source/api_documentation_guidelines.md37
-rw-r--r--guides/source/asset_pipeline.md69
-rw-r--r--guides/source/association_basics.md2
-rw-r--r--guides/source/caching_with_rails.md6
-rw-r--r--guides/source/command_line.md62
-rw-r--r--guides/source/configuring.md22
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md241
-rw-r--r--guides/source/credits.html.erb2
-rw-r--r--guides/source/debugging_rails_applications.md684
-rw-r--r--guides/source/development_dependencies_install.md19
-rw-r--r--guides/source/engines.md36
-rw-r--r--guides/source/form_helpers.md29
-rw-r--r--guides/source/generators.md28
-rw-r--r--guides/source/getting_started.md490
-rw-r--r--guides/source/i18n.md127
-rw-r--r--guides/source/index.html.erb1
-rw-r--r--guides/source/initialization.md161
-rw-r--r--guides/source/layout.html.erb5
-rw-r--r--guides/source/maintenance_policy.md6
-rw-r--r--guides/source/migrations.md38
-rw-r--r--guides/source/nested_model_forms.md6
-rw-r--r--guides/source/plugins.md14
-rw-r--r--guides/source/rails_application_templates.md4
-rw-r--r--guides/source/rails_on_rack.md13
-rw-r--r--guides/source/routing.md28
-rw-r--r--guides/source/security.md37
-rw-r--r--guides/source/testing.md22
-rw-r--r--guides/source/upgrading_ruby_on_rails.md23
-rw-r--r--guides/w3c_validator.rb2
-rw-r--r--railties/CHANGELOG.md25
-rw-r--r--railties/lib/rails/application.rb19
-rw-r--r--railties/lib/rails/application/configuration.rb2
-rw-r--r--railties/lib/rails/commands/console.rb36
-rw-r--r--railties/lib/rails/commands/dbconsole.rb2
-rw-r--r--railties/lib/rails/commands/server.rb13
-rw-r--r--railties/lib/rails/generators/app_base.rb4
-rw-r--r--railties/lib/rails/generators/base.rb4
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb9
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb21
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml27
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml26
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb1
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb22
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec4
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile14
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb4
-rw-r--r--railties/lib/rails/rack.rb2
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/test_help.rb1
-rw-r--r--railties/test/application/assets_test.rb41
-rw-r--r--railties/test/application/configuration_test.rb102
-rw-r--r--railties/test/application/multiple_applications_test.rb26
-rw-r--r--railties/test/application/rake/migrations_test.rb2
-rw-r--r--railties/test/commands/console_test.rb32
-rw-r--r--railties/test/generators/app_generator_test.rb49
-rw-r--r--railties/test/generators/argv_scrubber_test.rb2
-rw-r--r--railties/test/generators/plugin_generator_test.rb49
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb7
-rw-r--r--railties/test/railties/engine_test.rb11
-rw-r--r--railties/test/railties/mounted_engine_test.rb10
-rw-r--r--railties/test/railties/railtie_test.rb8
470 files changed, 9964 insertions, 4365 deletions
diff --git a/.travis.yml b/.travis.yml
index 9e7a449010..e5aaf57f93 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,8 @@ before_install:
rvm:
- 1.9.3
- 2.0.0
- - 2.1.1
+ - 2.1
+ - ruby-head
- rbx-2
- jruby
env:
@@ -19,6 +20,7 @@ matrix:
allow_failures:
- rvm: rbx-2
- rvm: jruby
+ - rvm: ruby-head
fast_finish: true
notifications:
email: false
diff --git a/Gemfile b/Gemfile
index 4a891bf6b5..2a695df618 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,6 +11,9 @@ gem 'rack-cache', '~> 1.2'
gem 'jquery-rails', '~> 3.1.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
+gem 'arel', github: 'rails/arel', branch: 'master'
+gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: 'master'
+gem 'i18n', github: 'svenfuchs/i18n', branch: 'master'
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
@@ -23,7 +26,7 @@ gem 'uglifier', '>= 1.3.0', require: false
group :doc do
gem 'sdoc', '~> 0.4.0'
- gem 'redcarpet', '~> 2.2.2', platforms: :ruby
+ gem 'redcarpet', '~> 3.1.2', platforms: :ruby
gem 'w3c_validators'
gem 'kindlerb'
end
@@ -36,6 +39,9 @@ local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
instance_eval File.read local_gemfile if File.exist? local_gemfile
group :test do
+ # FIX: Our test suite isn't ready to run in random order yet
+ gem 'minitest', '< 5.3.4'
+
platforms :mri_19 do
gem 'ruby-prof', '~> 0.11.2'
end
@@ -83,7 +89,7 @@ end
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED']
platforms :ruby do
- gem 'ruby-oci8', '>= 2.0.4'
+ gem 'ruby-oci8', '~> 2.1'
end
gem 'activerecord-oracle_enhanced-adapter', github: 'rsim/oracle-enhanced', branch: 'master'
end
diff --git a/README.md b/README.md
index 7f079536d2..c3577efe53 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the
## Code Status
-* [![Build Status](https://travis-ci.org/rails/rails.png?branch=master)](https://travis-ci.org/rails/rails)
+* [![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails)
## License
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 951a3e5fb5..652ce0b3d8 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -154,7 +154,7 @@ module ActionMailer
# * signup_notification.text.erb
# * signup_notification.html.erb
# * signup_notification.xml.builder
- # * signup_notification.yaml.erb
+ # * signup_notification.yml.erb
#
# Each would be rendered and added as a separate part to the message, with the corresponding content
# type. The content type for the entire message is automatically set to <tt>multipart/alternative</tt>,
@@ -325,7 +325,7 @@ module ActionMailer
# directory can be configured using the <tt>preview_path</tt> option which has a default
# of <tt>test/mailers/previews</tt>:
#
- # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
+ # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"
#
# An overview of all previews is accessible at <tt>http://localhost:3000/rails/mailers</tt>
# on a running development server instance.
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index b66e5bd6a3..229ded8e04 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -10,10 +10,15 @@ require 'mailers/proc_mailer'
require 'mailers/asset_mailer'
class BaseTest < ActiveSupport::TestCase
- def teardown
- ActionMailer::Base.asset_host = nil
- ActionMailer::Base.assets_dir = nil
- ActionMailer::Base.preview_interceptors.clear
+ setup do
+ @original_asset_host = ActionMailer::Base.asset_host
+ @original_assets_dir = ActionMailer::Base.assets_dir
+ end
+
+ teardown do
+ ActionMailer::Base.asset_host = @original_asset_host
+ ActionMailer::Base.assets_dir = @original_assets_dir
+ BaseMailer.deliveries.clear
end
test "method call to mail does not raise error" do
@@ -104,6 +109,7 @@ class BaseTest < ActiveSupport::TestCase
test "attachment gets content type from filename" do
email = BaseMailer.attachment_with_content
assert_equal('invoice.pdf', email.attachments[0].filename)
+ assert_equal('application/pdf', email.attachments[0].mime_type)
end
test "attachment with hash" do
@@ -201,25 +207,29 @@ class BaseTest < ActiveSupport::TestCase
end
test "subject gets default from I18n" do
- BaseMailer.default subject: nil
- email = BaseMailer.welcome(subject: nil)
- assert_equal "Welcome", email.subject
+ with_default BaseMailer, subject: nil do
+ email = BaseMailer.welcome(subject: nil)
+ assert_equal "Welcome", email.subject
- I18n.backend.store_translations('en', base_mailer: {welcome: {subject: "New Subject!"}})
- email = BaseMailer.welcome(subject: nil)
- assert_equal "New Subject!", email.subject
+ with_translation 'en', base_mailer: {welcome: {subject: "New Subject!"}} do
+ email = BaseMailer.welcome(subject: nil)
+ assert_equal "New Subject!", email.subject
+ end
+ end
end
test 'default subject can have interpolations' do
- I18n.backend.store_translations('en', base_mailer: {with_subject_interpolations: {subject: 'Will the real %{rapper_or_impersonator} please stand up?'}})
- email = BaseMailer.with_subject_interpolations
- assert_equal 'Will the real Slim Shady please stand up?', email.subject
+ with_translation 'en', base_mailer: {with_subject_interpolations: {subject: 'Will the real %{rapper_or_impersonator} please stand up?'}} do
+ email = BaseMailer.with_subject_interpolations
+ assert_equal 'Will the real Slim Shady please stand up?', email.subject
+ end
end
test "translations are scoped properly" do
- I18n.backend.store_translations('en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}})
- email = BaseMailer.email_with_translations
- assert_equal 'Hello lifo!', email.body.encoded
+ with_translation 'en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}} do
+ email = BaseMailer.email_with_translations
+ assert_equal 'Hello lifo!', email.body.encoded
+ end
end
# Implicit multipart
@@ -407,14 +417,12 @@ class BaseTest < ActiveSupport::TestCase
end
test "calling just the action should return the generated mail object" do
- BaseMailer.deliveries.clear
email = BaseMailer.welcome
assert_equal(0, BaseMailer.deliveries.length)
assert_equal('The first email on new API!', email.subject)
end
test "calling deliver on the action should deliver the mail object" do
- BaseMailer.deliveries.clear
BaseMailer.expects(:deliver_mail).once
mail = BaseMailer.welcome.deliver
assert_equal 'The first email on new API!', mail.subject
@@ -422,7 +430,6 @@ class BaseTest < ActiveSupport::TestCase
test "calling deliver on the action should increment the deliveries collection if using the test mailer" do
BaseMailer.delivery_method = :test
- BaseMailer.deliveries.clear
BaseMailer.welcome.deliver
assert_equal(1, BaseMailer.deliveries.length)
end
@@ -442,7 +449,6 @@ class BaseTest < ActiveSupport::TestCase
end
test "should raise if missing template in implicit render" do
- BaseMailer.deliveries.clear
assert_raises ActionView::MissingTemplate do
BaseMailer.implicit_different_template('missing_template').deliver
end
@@ -479,18 +485,17 @@ class BaseTest < ActiveSupport::TestCase
end
test "assets tags should use a Mailer's asset_host settings when available" do
- begin
- ActionMailer::Base.config.asset_host = "http://global.com"
- ActionMailer::Base.config.assets_dir = "global/"
+ ActionMailer::Base.config.asset_host = "http://global.com"
+ ActionMailer::Base.config.assets_dir = "global/"
- AssetMailer.asset_host = "http://local.com"
+ TempAssetMailer = Class.new(AssetMailer) do
+ self.mailer_name = "asset_mailer"
+ self.asset_host = "http://local.com"
+ end
- mail = AssetMailer.welcome
+ mail = TempAssetMailer.welcome
- assert_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip)
- ensure
- AssetMailer.asset_host = ActionMailer::Base.config.asset_host
- end
+ assert_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip)
end
test 'the view is not rendered when mail was never called' do
@@ -518,32 +523,40 @@ class BaseTest < ActiveSupport::TestCase
end
test "you can register an observer to the mail object that gets informed on email delivery" do
- ActionMailer::Base.register_observer(MyObserver)
- mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- mail.deliver
+ mail_side_effects do
+ ActionMailer::Base.register_observer(MyObserver)
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
end
test "you can register an observer using its stringified name to the mail object that gets informed on email delivery" do
- ActionMailer::Base.register_observer("BaseTest::MyObserver")
- mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- mail.deliver
+ mail_side_effects do
+ ActionMailer::Base.register_observer("BaseTest::MyObserver")
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
end
test "you can register an observer using its symbolized underscored name to the mail object that gets informed on email delivery" do
- ActionMailer::Base.register_observer(:"base_test/my_observer")
- mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- mail.deliver
+ mail_side_effects do
+ ActionMailer::Base.register_observer(:"base_test/my_observer")
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
end
test "you can register multiple observers to the mail object that both get informed on email delivery" do
- ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
- mail = BaseMailer.welcome
- MyObserver.expects(:delivered_email).with(mail)
- MySecondObserver.expects(:delivered_email).with(mail)
- mail.deliver
+ mail_side_effects do
+ ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
+ mail = BaseMailer.welcome
+ MyObserver.expects(:delivered_email).with(mail)
+ MySecondObserver.expects(:delivered_email).with(mail)
+ mail.deliver
+ end
end
class MyInterceptor
@@ -556,72 +569,41 @@ class BaseTest < ActiveSupport::TestCase
def self.previewing_email(mail); end
end
- class BaseMailerPreview < ActionMailer::Preview
- def welcome
- BaseMailer.welcome
- end
- end
-
test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do
- ActionMailer::Base.register_interceptor(MyInterceptor)
- mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- mail.deliver
+ mail_side_effects do
+ ActionMailer::Base.register_interceptor(MyInterceptor)
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
end
test "you can register an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
- ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
- mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- mail.deliver
+ mail_side_effects do
+ ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
end
test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before delivery" do
- ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
- mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- mail.deliver
+ mail_side_effects do
+ ActionMailer::Base.register_interceptor(:"base_test/my_interceptor")
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
end
test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
- ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
- mail = BaseMailer.welcome
- MyInterceptor.expects(:delivering_email).with(mail)
- MySecondInterceptor.expects(:delivering_email).with(mail)
- mail.deliver
- end
-
- test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
- ActionMailer::Base.register_preview_interceptor(MyInterceptor)
- mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
- end
-
- test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
- ActionMailer::Base.register_preview_interceptor("BaseTest::MyInterceptor")
- mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
- end
-
- test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
- ActionMailer::Base.register_preview_interceptor(:"base_test/my_interceptor")
- mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
- end
-
- test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
- ActionMailer::Base.register_preview_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
- mail = BaseMailer.welcome
- BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
- MyInterceptor.expects(:previewing_email).with(mail)
- MySecondInterceptor.expects(:previewing_email).with(mail)
- BaseMailerPreview.call(:welcome)
+ mail_side_effects do
+ ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
+ mail = BaseMailer.welcome
+ MyInterceptor.expects(:delivering_email).with(mail)
+ MySecondInterceptor.expects(:delivering_email).with(mail)
+ mail.deliver
+ end
end
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
@@ -770,4 +752,77 @@ class BaseTest < ActiveSupport::TestCase
ensure
klass.default_params = old
end
+
+ # A simple hack to restore the observers and interceptors for Mail, as it
+ # does not have an unregister API yet.
+ def mail_side_effects
+ old_observers = Mail.class_variable_get(:@@delivery_notification_observers)
+ old_delivery_interceptors = Mail.class_variable_get(:@@delivery_interceptors)
+ yield
+ ensure
+ Mail.class_variable_set(:@@delivery_notification_observers, old_observers)
+ Mail.class_variable_set(:@@delivery_interceptors, old_delivery_interceptors)
+ end
+
+ def with_translation(locale, data)
+ I18n.backend.store_translations(locale, data)
+ yield
+ ensure
+ I18n.backend.reload!
+ end
+end
+
+class BasePreviewInterceptorsTest < ActiveSupport::TestCase
+ teardown do
+ ActionMailer::Base.preview_interceptors.clear
+ end
+
+ class BaseMailerPreview < ActionMailer::Preview
+ def welcome
+ BaseMailer.welcome
+ end
+ end
+
+ class MyInterceptor
+ def self.delivering_email(mail); end
+ def self.previewing_email(mail); end
+ end
+
+ class MySecondInterceptor
+ def self.delivering_email(mail); end
+ def self.previewing_email(mail); end
+ end
+
+ test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor(MyInterceptor)
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor("BasePreviewInterceptorsTest::MyInterceptor")
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptor(:"base_preview_interceptors_test/my_interceptor")
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
+
+ test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do
+ ActionMailer::Base.register_preview_interceptors("BasePreviewInterceptorsTest::MyInterceptor", MySecondInterceptor)
+ mail = BaseMailer.welcome
+ BaseMailerPreview.any_instance.stubs(:welcome).returns(mail)
+ MyInterceptor.expects(:previewing_email).with(mail)
+ MySecondInterceptor.expects(:previewing_email).with(mail)
+ BaseMailerPreview.call(:welcome)
+ end
end
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 609903620b..16e8638542 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -47,12 +47,12 @@ class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
end
class CustomDeliveryMethodsTest < ActiveSupport::TestCase
- def setup
+ setup do
@old_delivery_method = ActionMailer::Base.delivery_method
ActionMailer::Base.add_delivery_method :custom, MyCustomDelivery
end
- def teardown
+ teardown do
ActionMailer::Base.delivery_method = @old_delivery_method
new = ActionMailer::Base.delivery_methods.dup
new.delete(:custom)
@@ -93,18 +93,16 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
end
- def setup
- ActionMailer::Base.delivery_method = :smtp
+ setup do
+ @old_delivery_method = DeliveryMailer.delivery_method
end
- def teardown
- DeliveryMailer.delivery_method = :smtp
- DeliveryMailer.perform_deliveries = true
- DeliveryMailer.raise_delivery_errors = true
+ teardown do
+ DeliveryMailer.delivery_method = @old_delivery_method
+ DeliveryMailer.deliveries.clear
end
test "ActionMailer should be told when Mail gets delivered" do
- DeliveryMailer.deliveries.clear
DeliveryMailer.expects(:deliver_mail).once
DeliveryMailer.welcome.deliver
end
@@ -176,22 +174,29 @@ class MailDeliveryTest < ActiveSupport::TestCase
end
test "does not perform deliveries if requested" do
- DeliveryMailer.perform_deliveries = false
- DeliveryMailer.deliveries.clear
- Mail::Message.any_instance.expects(:deliver!).never
- DeliveryMailer.welcome.deliver
+ old_perform_deliveries = DeliveryMailer.perform_deliveries
+ begin
+ DeliveryMailer.perform_deliveries = false
+ Mail::Message.any_instance.expects(:deliver!).never
+ DeliveryMailer.welcome.deliver
+ ensure
+ DeliveryMailer.perform_deliveries = old_perform_deliveries
+ end
end
test "does not append the deliveries collection if told not to perform the delivery" do
- DeliveryMailer.perform_deliveries = false
- DeliveryMailer.deliveries.clear
- DeliveryMailer.welcome.deliver
- assert_equal(0, DeliveryMailer.deliveries.length)
+ old_perform_deliveries = DeliveryMailer.perform_deliveries
+ begin
+ DeliveryMailer.perform_deliveries = false
+ DeliveryMailer.welcome.deliver
+ assert_equal [], DeliveryMailer.deliveries
+ ensure
+ DeliveryMailer.perform_deliveries = old_perform_deliveries
+ end
end
test "raise errors on bogus deliveries" do
DeliveryMailer.delivery_method = BogusDelivery
- DeliveryMailer.deliveries.clear
assert_raise RuntimeError do
DeliveryMailer.welcome.deliver
end
@@ -199,27 +204,34 @@ class MailDeliveryTest < ActiveSupport::TestCase
test "does not increment the deliveries collection on error" do
DeliveryMailer.delivery_method = BogusDelivery
- DeliveryMailer.deliveries.clear
assert_raise RuntimeError do
DeliveryMailer.welcome.deliver
end
- assert_equal(0, DeliveryMailer.deliveries.length)
+ assert_equal [], DeliveryMailer.deliveries
end
test "does not raise errors on bogus deliveries if set" do
- DeliveryMailer.delivery_method = BogusDelivery
- DeliveryMailer.raise_delivery_errors = false
- assert_nothing_raised do
- DeliveryMailer.welcome.deliver
+ old_raise_delivery_errors = DeliveryMailer.raise_delivery_errors
+ begin
+ DeliveryMailer.delivery_method = BogusDelivery
+ DeliveryMailer.raise_delivery_errors = false
+ assert_nothing_raised do
+ DeliveryMailer.welcome.deliver
+ end
+ ensure
+ DeliveryMailer.raise_delivery_errors = old_raise_delivery_errors
end
end
test "does not increment the deliveries collection on bogus deliveries" do
- DeliveryMailer.delivery_method = BogusDelivery
- DeliveryMailer.raise_delivery_errors = false
- DeliveryMailer.deliveries.clear
- DeliveryMailer.welcome.deliver
- assert_equal(0, DeliveryMailer.deliveries.length)
+ old_raise_delivery_errors = DeliveryMailer.raise_delivery_errors
+ begin
+ DeliveryMailer.delivery_method = BogusDelivery
+ DeliveryMailer.raise_delivery_errors = false
+ DeliveryMailer.welcome.deliver
+ assert_equal [], DeliveryMailer.deliveries
+ ensure
+ DeliveryMailer.raise_delivery_errors = old_raise_delivery_errors
+ end
end
-
end
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 14a1b11b6d..d502d42ffd 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -10,15 +10,15 @@ class I18nTestMailer < ActionMailer::Base
def mail_with_i18n_subject(recipient)
@recipient = recipient
I18n.locale = :de
- mail(to: recipient, subject: "#{I18n.t :email_subject} #{recipient}",
+ mail(to: recipient, subject: I18n.t(:email_subject),
from: "system@loudthinking.com", date: Time.local(2004, 12, 12))
end
end
class TestController < ActionController::Base
def send_mail
- I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver
- render text: 'Mail sent'
+ email = I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver
+ render text: "Mail sent - Subject: #{email.subject}"
end
end
@@ -32,16 +32,19 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes
end
- def setup
- I18n.backend.store_translations('de', email_subject: '[Signed up] Welcome')
+ def test_send_mail
+ with_translation 'de', email_subject: '[Anmeldung] Willkommen' do
+ get '/test/send_mail'
+ assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
+ end
end
- def teardown
- I18n.locale = :en
- end
+ protected
- def test_send_mail
- get '/test/send_mail'
- assert_equal "Mail sent", @response.body
+ def with_translation(locale, data)
+ I18n.backend.store_translations(locale, data)
+ yield
+ ensure
+ I18n.backend.reload!
end
end
diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb
index 5f0bee88fd..e7a73d6c8e 100644
--- a/actionmailer/test/log_subscriber_test.rb
+++ b/actionmailer/test/log_subscriber_test.rb
@@ -1,7 +1,7 @@
-require "abstract_unit"
+require 'abstract_unit'
require 'mailers/base_mailer'
-require "active_support/log_subscriber/test_helper"
-require "action_mailer/log_subscriber"
+require 'active_support/log_subscriber/test_helper'
+require 'action_mailer/log_subscriber'
class AMLogSubscriberTest < ActionMailer::TestCase
include ActiveSupport::LogSubscriber::TestHelper
@@ -31,6 +31,8 @@ class AMLogSubscriberTest < ActionMailer::TestCase
assert_equal(2, @logger.logged(:debug).size)
assert_match(/BaseMailer#welcome: processed outbound mail in [\d.]+ms/, @logger.logged(:debug).first)
assert_match(/Welcome/, @logger.logged(:debug).second)
+ ensure
+ BaseMailer.deliveries.clear
end
def test_receive_is_notified
diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb
index 7f959282cb..166dd096d4 100644
--- a/actionmailer/test/mail_layout_test.rb
+++ b/actionmailer/test/mail_layout_test.rb
@@ -44,16 +44,6 @@ class ExplicitLayoutMailer < ActionMailer::Base
end
class LayoutMailerTest < ActiveSupport::TestCase
- def setup
- set_delivery_method :test
- ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries.clear
- end
-
- def teardown
- restore_delivery_method
- end
-
def test_should_pickup_default_layout
mail = AutoLayoutMailer.hello
assert_equal "Hello from layout Inside", mail.body.to_s.strip
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index e70e0b5fa7..be1f53faf5 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,9 +1,107 @@
+* Fix 'Stack level too deep' when rendering `head :ok` in an action method
+ called 'status' in a controller.
+
+ Fixes #13905.
+
+ *Christiaan Van den Poel*
+
+* Add MKCALENDAR HTTP method (RFC 4791).
+
+ *Sergey Karpesh*
+
+* Instrument fragment cache metrics.
+
+ Adds `:controller`: and `:action` keys to the instrumentation payload
+ for the `*_fragment.action_controller` notifications. This allows tracking
+ e.g. the fragment cache hit rates for each controller action.
+
+ *Daniel Schierbeck*
+
+* Always use the provided port if the protocol is relative.
+
+ Fixes #15043.
+
+ *Guilherme Cavalcanti*, *Andrew White*
+
+* Moved `params[request_forgery_protection_token]` into its own method
+ and improved tests.
+
+ Fixes #11316.
+
+ *Tom Kadwill*
+
+* Added verification of route constraints given as a Proc or an object responding
+ to `:matches?`. Previously, when given an non-complying object, it would just
+ silently fail to enforce the constraint. It will now raise an `ArgumentError`
+ when setting up the routes.
+
+ *Xavier Defrang*
+
+* Properly treat the entire IPv6 User Local Address space as private for
+ purposes of remote IP detection. Also handle uppercase private IPv6
+ addresses.
+
+ Fixes #12638.
+
+ *Caleb Spare*
+
+* Fixed an issue with migrating legacy json cookies.
+
+ Previously, the `VerifyAndUpgradeLegacySignedMessage` assumes all incoming
+ cookies are marshal-encoded. This is not the case when `secret_token` is
+ used in conjunction with the `:json` or `:hybrid` serializer.
+
+ In those case, when upgrading to use `secret_key_base`, this would cause a
+ `TypeError: incompatible marshal file format` and a 500 error for the user.
+
+ Fixes #14774.
+
+ *Godfrey Chan*
+
+* Make URL escaping more consistent:
+
+ 1. Escape '%' characters in URLs - only unescaped data should be passed to URL helpers
+ 2. Add an `escape_segment` helper to `Router::Utils` that escapes '/' characters
+ 3. Use `escape_segment` rather than `escape_fragment` in optimized URL generation
+ 4. Use `escape_segment` rather than `escape_path` in URL generation
+
+ For point 4 there are two exceptions. Firstly, when a route uses wildcard segments
+ (e.g. *foo) then we use `escape_path` as the value may contain '/' characters. This
+ means that wildcard routes can't be optimized. Secondly, if a `:controller` segment
+ is used in the path then this uses `escape_path` as the controller may be namespaced.
+
+ Fixes #14629, #14636 and #14070.
+
+ *Andrew White*, *Edho Arief*
+
+* Add alias `ActionDispatch::Http::UploadedFile#to_io` to
+ `ActionDispatch::Http::UploadedFile#tempfile`.
+
+ *Tim Linquist*
+
+* Returns null type format when format is not know and controller is using `any`
+ format block.
+
+ Fixes #14462.
+
+ *Rafael Mendonça França*
+
+* Improve routing error page with fuzzy matching search.
+
+ *Winston*
+
+* Only make deeply nested routes shallow when parent is shallow.
+
+ Fixes #14684.
+
+ *Andrew White*, *James Coglan*
+
* Append link to bad code to backtrace when exception is SyntaxError.
*Boris Kuznetsov*
-
+
* Swapped the parameters of assert_equal in `assert_select` so that the
- proper values were printed correctly
+ proper values were printed correctly
Fixes #14422.
diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc
index ad1448f61b..2f923136d9 100644
--- a/actionpack/RUNNING_UNIT_TESTS.rdoc
+++ b/actionpack/RUNNING_UNIT_TESTS.rdoc
@@ -8,10 +8,10 @@ Rake can be found at http://rake.rubyforge.org.
== Running by hand
-To run a single test suite
+Run a single test suite:
rake test TEST=path/to/test.rb
-which can be further narrowed down to one test:
+Run one test in a test suite:
rake test TEST=path/to/test.rb TESTOPTS="--name=test_something"
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index af5de815bb..c00f0d0c6f 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -8,7 +8,8 @@ module AbstractController
class Error < StandardError #:nodoc:
end
- class ActionNotFound < StandardError #:nodoc:
+ # Raised when a non-existing controller action is triggered.
+ class ActionNotFound < StandardError
end
# <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
@@ -120,14 +121,14 @@ module AbstractController
#
# The actual method that is called is determined by calling
# #method_for_action. If no method can handle the action, then an
- # ActionNotFound error is raised.
+ # AbstractController::ActionNotFound error is raised.
#
# ==== Returns
# * <tt>self</tt>
def process(action, *args)
- @_action_name = action_name = action.to_s
+ @_action_name = action.to_s
- unless action_name = method_for_action(action_name)
+ unless action_name = _find_action_name(@_action_name)
raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
end
@@ -160,7 +161,7 @@ module AbstractController
# ==== Returns
# * <tt>TrueClass</tt>, <tt>FalseClass</tt>
def available_action?(action_name)
- method_for_action(action_name).present?
+ _find_action_name(action_name).present?
end
private
@@ -204,6 +205,24 @@ module AbstractController
end
# Takes an action name and returns the name of the method that will
+ # handle the action.
+ #
+ # It checks if the action name is valid and returns false otherwise.
+ #
+ # See method_for_action for more information.
+ #
+ # ==== Parameters
+ # * <tt>action_name</tt> - An action name to find a method name for
+ #
+ # ==== Returns
+ # * <tt>string</tt> - The name of the method that handles the action
+ # * false - No valid method name could be found.
+ # Raise AbstractController::ActionNotFound.
+ def _find_action_name(action_name)
+ _valid_action_name?(action_name) && method_for_action(action_name)
+ end
+
+ # Takes an action name and returns the name of the method that will
# handle the action. In normal cases, this method returns the same
# name as it receives. By default, if #method_for_action receives
# a name that is not an action, it will look for an #action_missing
@@ -218,14 +237,14 @@ module AbstractController
# the case.
#
# If none of these conditions are true, and method_for_action
- # returns nil, an ActionNotFound exception will be raised.
+ # returns nil, an AbstractController::ActionNotFound exception will be raised.
#
# ==== Parameters
# * <tt>action_name</tt> - An action name to find a method name for
#
# ==== Returns
# * <tt>string</tt> - The name of the method that handles the action
- # * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.
+ # * <tt>nil</tt> - No method name could be found.
def method_for_action(action_name)
if action_method?(action_name)
action_name
@@ -233,5 +252,10 @@ module AbstractController
"_handle_action_missing"
end
end
+
+ # Checks if the action name is valid and returns false otherwise.
+ def _valid_action_name?(action_name)
+ action_name.to_s !~ Regexp.new(File::SEPARATOR)
+ end
end
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index d6c941832f..69aca308d6 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -178,41 +178,35 @@ module AbstractController
# set up before_action, prepend_before_action, skip_before_action, etc.
# for each of before, after, and around.
[:before, :after, :around].each do |callback|
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- # Append a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def #{callback}_action(*names, &blk) # def before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
-
- alias_method :#{callback}_filter, :#{callback}_action
-
- # Prepend a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
- end # end
- end # end
-
- alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action
-
- # Skip a before, after or around callback. See _insert_callbacks
- # for details on the allowed parameters.
- def skip_#{callback}_action(*names) # def skip_before_action(*names)
- _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
- skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options)
- end # end
- end # end
-
- alias_method :skip_#{callback}_filter, :skip_#{callback}_action
-
- # *_action is the same as append_*_action
- alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action
- alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action
- RUBY_EVAL
+ define_method "#{callback}_action" do |*names, &blk|
+ _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, callback, name, options)
+ end
+ end
+
+ alias_method :"#{callback}_filter", :"#{callback}_action"
+
+ define_method "prepend_#{callback}_action" do |*names, &blk|
+ _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, callback, name, options.merge(:prepend => true))
+ end
+ end
+
+ alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action"
+
+ # Skip a before, after or around callback. See _insert_callbacks
+ # for details on the allowed parameters.
+ define_method "skip_#{callback}_action" do |*names|
+ _insert_callbacks(names) do |name, options|
+ skip_callback(:process_action, callback, name, options)
+ end
+ end
+
+ alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action"
+
+ # *_action is the same as append_*_action
+ alias_method :"append_#{callback}_action", :"#{callback}_action" # alias_method :append_before_action, :before_action
+ alias_method :"append_#{callback}_filter", :"#{callback}_action" # alias_method :append_before_filter, :before_action
end
end
end
diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb
index 4a95e1f276..72d07b0927 100644
--- a/actionpack/lib/abstract_controller/url_for.rb
+++ b/actionpack/lib/abstract_controller/url_for.rb
@@ -11,7 +11,7 @@ module AbstractController
def _routes
raise "In order to use #url_for, you must include routing helpers explicitly. " \
- "For instance, `include Rails.application.routes.url_helpers"
+ "For instance, `include Rails.application.routes.url_helpers`."
end
module ClassMethods
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 879d5fdd94..2694d4c12f 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -90,7 +90,13 @@ module ActionController
end
def instrument_fragment_cache(name, key) # :nodoc:
- ActiveSupport::Notifications.instrument("#{name}.action_controller", :key => key){ yield }
+ payload = {
+ controller: controller_name,
+ action: action_name,
+ key: key
+ }
+
+ ActiveSupport::Notifications.instrument("#{name}.action_controller", payload) { yield }
end
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index b84c9e78c3..696fbf6e09 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -70,7 +70,8 @@ module ActionController
# can do the following:
#
# class HelloController < ActionController::Metal
- # include ActionController::Rendering
+ # include AbstractController::Rendering
+ # include ActionView::Layouts
# append_view_path "#{Rails.root}/app/views"
#
# def index
@@ -231,5 +232,9 @@ module ActionController
new.dispatch(name, klass.new(env))
end
end
+
+ def _status_code
+ @_status
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 43407f5b78..84a9112144 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -27,7 +27,7 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- if include_content?(self.status)
+ if include_content?(self._status_code)
self.content_type = content_type || (Mime[formats.first] if formats)
self.response.charset = false if self.response
self.response_body = " "
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 2eb7853aa6..3111992f82 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -90,17 +90,29 @@ module ActionController
end
def authenticate(request, &login_procedure)
- unless request.authorization.blank?
+ if has_basic_credentials?(request)
login_procedure.call(*user_name_and_password(request))
end
end
+ def has_basic_credentials?(request)
+ request.authorization.present? && (auth_scheme(request) == 'Basic')
+ end
+
def user_name_and_password(request)
decode_credentials(request).split(':', 2)
end
def decode_credentials(request)
- ::Base64.decode64(request.authorization.split(' ', 2).last || '')
+ ::Base64.decode64(auth_param(request) || '')
+ end
+
+ def auth_scheme(request)
+ request.authorization.split(' ', 2).first
+ end
+
+ def auth_param(request)
+ request.authorization.split(' ', 2).second
end
def encode_credentials(user_name, password)
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index acf40b2e16..4c0554d27b 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -102,7 +102,8 @@ module ActionController
end
end
- @stream.write "data: #{json}\n\n"
+ message = json.gsub("\n", "\ndata: ")
+ @stream.write "data: #{message}\n\n"
end
end
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index bdf6e88699..6921834044 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -6,7 +6,7 @@ module ActionController
extend ActiveSupport::Concern
delegate :headers, :status=, :location=, :content_type=,
- :status, :location, :content_type, :to => "@_response"
+ :status, :location, :content_type, :_status_code, :to => "@_response"
def dispatch(action, request)
set_response!(request)
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 2812038938..136e086d0d 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -14,7 +14,7 @@ module ActionController
include ActionController::RackDelegation
include ActionController::UrlFor
- # Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
+ # Redirects the browser to the target specified in +options+. This parameter can be any one of:
#
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
@@ -24,6 +24,8 @@ module ActionController
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
+ # === Examples:
+ #
# redirect_to action: "show", id: 5
# redirect_to post
# redirect_to "http://www.rubyonrails.org"
@@ -32,7 +34,7 @@ module ActionController
# redirect_to :back
# redirect_to proc { edit_post_url(@post) }
#
- # The redirection happens as a "302 Found" header unless otherwise specified.
+ # The redirection happens as a "302 Found" header unless otherwise specified using the <tt>:status</tt> option:
#
# redirect_to post_url(@post), status: :found
# redirect_to action: 'atom', status: :moved_permanently
@@ -60,8 +62,10 @@ module ActionController
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
#
- # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
- # behavior for this case by rescuing ActionController::RedirectBackError.
+ # When using <tt>redirect_to :back</tt>, if there is no referrer,
+ # <tt>ActionController::RedirectBackError</tt> will be raised. You
+ # may specify some fallback behavior for this case by rescuing
+ # <tt>ActionController::RedirectBackError</tt>.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 6c7b4652d4..29ce5abd55 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -6,6 +6,11 @@ module ActionController
Renderers.add(key, &block)
end
+ # See <tt>Renderers.remove</tt>
+ def self.remove_renderer(key)
+ Renderers.remove(key)
+ end
+
class MissingRenderer < LoadError
def initialize(format)
super "No renderer defined for format: #{format}"
@@ -42,8 +47,8 @@ module ActionController
nil
end
- # Hash of available renderers, mapping a renderer name to its proc.
- # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
+ # A Set containing renderer names that correspond to available renderer procs.
+ # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
RENDERERS = Set.new
# Adds a new renderer to call within controller actions.
@@ -83,6 +88,17 @@ module ActionController
RENDERERS << key.to_sym
end
+ # This method is the opposite of add method.
+ #
+ # Usage:
+ #
+ # ActionController::Renderers.remove(:csv)
+ def self.remove(key)
+ RENDERERS.delete(key.to_sym)
+ method = "_render_option_#{key}"
+ remove_method(method) if method_defined?(method)
+ end
+
module All
extend ActiveSupport::Concern
include Renderers
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index e3b1f5ae7c..1355fe87d0 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -247,7 +247,7 @@ module ActionController #:nodoc:
# * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?
!protect_against_forgery? || request.get? || request.head? ||
- form_authenticity_token == params[request_forgery_protection_token] ||
+ form_authenticity_token == form_authenticity_param ||
form_authenticity_token == request.headers['X-CSRF-Token']
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index e24b56fa91..5096558c67 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -22,7 +22,7 @@ module ActionController #:nodoc:
#
# 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it.
#
- # === Builtin HTTP verb semantics
+ # === Built-in HTTP verb semantics
#
# The default \Rails responder holds semantics for each HTTP verb. Depending on the
# content type, verb and the resource status, it will behave differently.
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 754249cbc8..37d4a96ee1 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -30,9 +30,9 @@ module ActionController
:_recall => request.symbolized_path_parameters
).freeze
- if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
+ if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) ||
(script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['ORIGINAL_SCRIPT_NAME'])
+ (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze])
@_url_options.dup.tap do |options|
if original_script_name
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index df57efaa97..c6a8f581de 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -17,8 +17,9 @@ module ActionController
@_templates = Hash.new(0)
@_layouts = Hash.new(0)
@_files = Hash.new(0)
+ @_subscribers = []
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
+ @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:layout]
if path
@_layouts[path] += 1
@@ -28,7 +29,7 @@ module ActionController
end
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
+ @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
@@ -41,7 +42,7 @@ module ActionController
@_templates[path] += 1
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
+ @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
next if payload[:virtual_path] # files don't have virtual path
path = payload[:identifier]
@@ -53,8 +54,9 @@ module ActionController
end
def teardown_subscriptions
- ActiveSupport::Notifications.unsubscribe("render_template.action_view")
- ActiveSupport::Notifications.unsubscribe("!render_template.action_view")
+ @_subscribers.each do |subscriber|
+ ActiveSupport::Notifications.unsubscribe(subscriber)
+ end
end
def process(*args)
@@ -548,6 +550,31 @@ module ActionController
end
end
+ # Simulate a HTTP request to +action+ by specifying request method,
+ # parameters and set/volley the response.
+ #
+ # - +action+: The controller action to call.
+ # - +http_method+: Request method used to send the http request. Possible values
+ # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
+ # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
+ # string that is appropriately encoded (+application/x-www-form-urlencoded+
+ # or +multipart/form-data+).
+ # - +session+: A hash of parameters to store in the session. This may be +nil+.
+ # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
+ #
+ # Example calling +create+ action and sending two params:
+ #
+ # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' }
+ #
+ # Example sending parameters, +nil+ session and setting a flash message:
+ #
+ # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
+ #
+ # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
+ # prefer using #get, #post, #patch, #put, #delete and #head methods
+ # respectively which will make tests more expressive.
+ #
+ # Note that the request method is not verified.
def process(action, http_method = 'GET', *args)
check_required_ivars
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 289e204ac8..2b851cc28d 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -6,8 +6,8 @@ module ActionDispatch
module Http
# Allows you to specify sensitive parameters which will be replaced from
# the request log by looking in the query string of the request and all
- # subhashes of the params hash to filter. If a block is given, each key and
- # value of the params hash and all subhashes is passed to it, the value
+ # sub-hashes of the params hash to filter. If a block is given, each key and
+ # value of the params hash and all sub-hashes is passed to it, the value
# or key can be replaced using String#replace or similar method.
#
# env["action_dispatch.parameter_filter"] = [:password]
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 2666cd4b0a..3e607bbde1 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,5 +1,10 @@
module ActionDispatch
module Http
+ # Provides access to the request's HTTP headers from the environment.
+ #
+ # env = { "CONTENT_TYPE" => "text/plain" }
+ # headers = ActionDispatch::Http::Headers.new(env)
+ # headers["Content-Type"] # => "text/plain"
class Headers
CGI_VARIABLES = %w(
CONTENT_TYPE CONTENT_LENGTH
@@ -14,21 +19,32 @@ module ActionDispatch
include Enumerable
attr_reader :env
- def initialize(env = {})
+ def initialize(env = {}) # :nodoc:
@env = env
end
+ # Returns the value for the given key mapped to @env.
def [](key)
@env[env_name(key)]
end
+ # Sets the given value for the key mapped to @env.
def []=(key, value)
@env[env_name(key)] = value
end
- def key?(key); @env.key? key; end
+ def key?(key)
+ @env.key? env_name(key)
+ end
alias :include? :key?
+ # Returns the value for the given key mapped to @env.
+ #
+ # If the key is not found and an optional code block is not provided,
+ # raises a <tt>KeyError</tt> exception.
+ #
+ # If the code block is provided, then it will be run and
+ # its result returned.
def fetch(key, *args, &block)
@env.fetch env_name(key), *args, &block
end
@@ -37,12 +53,17 @@ module ActionDispatch
@env.each(&block)
end
+ # Returns a new Http::Headers instance containing the contents of
+ # <tt>headers_or_env</tt> and the original instance.
def merge(headers_or_env)
headers = Http::Headers.new(env.dup)
headers.merge!(headers_or_env)
headers
end
+ # Adds the contents of <tt>headers_or_env</tt> to original instance
+ # entries; duplicate keys are overwritten with the values from
+ # <tt>headers_or_env</tt>.
def merge!(headers_or_env)
headers_or_env.each do |key, value|
self[env_name(key)] = value
@@ -50,6 +71,8 @@ module ActionDispatch
end
private
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(key)
key = key.to_s
if key =~ HTTP_HEADER
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index b803ce8b6f..0b2b60d2e4 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -129,7 +129,7 @@ module ActionDispatch
end
end
- order.include?(Mime::ALL) ? formats.first : nil
+ order.include?(Mime::ALL) ? format : nil
end
protected
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index daa06e96e6..cdb3e44b3a 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -64,6 +64,7 @@ module ActionDispatch
# Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
# Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
# Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
+ # Calendar Extensions to WebDAV (http://www.ietf.org/rfc/rfc4791.txt)
# PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
@@ -71,9 +72,10 @@ module ActionDispatch
RFC3648 = %w(ORDERPATCH)
RFC3744 = %w(ACL)
RFC5323 = %w(SEARCH)
+ RFC4791 = %w(MKCALENDAR)
RFC5789 = %w(PATCH)
- HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
+ HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
HTTP_METHOD_LOOKUP = {}
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 3d27ff2b24..eaea93b730 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -296,6 +296,9 @@ module ActionDispatch # :nodoc:
cookies
end
+ def _status_code
+ @status
+ end
private
def before_committed
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index a8d2dc3950..45bf751d09 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -18,6 +18,7 @@ module ActionDispatch
# A +Tempfile+ object with the actual uploaded file. Note that some of
# its interface is available directly.
attr_accessor :tempfile
+ alias :to_io :tempfile
# A string with the headers of the multipart request.
attr_accessor :headers
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 6f5a52c568..a6c17f50a5 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -29,25 +29,37 @@ module ActionDispatch
extract_subdomains(host, tld_length).join('.')
end
- def url_for(options = {})
- options = options.dup
- path = options.delete(:script_name).to_s.chomp("/")
- path << options.delete(:path).to_s
+ def url_for(options)
+ unless options[:host] || options[:only_path]
+ raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
+ end
- params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
- params.reject! { |_,v| v.to_param.nil? }
+ path = options[:script_name].to_s.chomp("/")
+ path << options[:path].to_s
- result = build_host_url(options)
if options[:trailing_slash]
if path.include?('?')
- result << path.sub(/\?/, '/\&')
+ path.sub!(/\?/, '/\&')
else
- result << path.sub(/[^\/]\z|\A\z/, '\&/')
+ path.sub!(/[^\/]\z|\A\z/, '\&/')
end
- else
- result << path
end
- result << "?#{params.to_query}" unless params.empty?
+
+ result = path
+
+ unless options[:only_path]
+ result.prepend build_host_url(options)
+ end
+
+ if options.key? :params
+ params = options[:params].is_a?(Hash) ?
+ options[:params] :
+ { params: options[:params] }
+
+ params.reject! { |_,v| v.to_param.nil? }
+ result << "?#{params.to_query}" unless params.empty?
+ end
+
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
result
end
@@ -55,28 +67,25 @@ module ActionDispatch
private
def build_host_url(options)
- if options[:host].blank? && options[:only_path].blank?
- raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
+ if match = options[:host].match(HOST_REGEXP)
+ options[:protocol] ||= match[1] unless options[:protocol] == false
+ options[:host] = match[2]
+ options[:port] = match[3] unless options.key?(:port)
end
- result = ""
+ options[:protocol] = normalize_protocol(options)
+ options[:host] = normalize_host(options)
+ options[:port] = normalize_port(options)
- unless options[:only_path]
- if match = options[:host].match(HOST_REGEXP)
- options[:protocol] ||= match[1] unless options[:protocol] == false
- options[:host] = match[2]
- options[:port] = match[3] unless options.key?(:port)
- end
-
- options[:protocol] = normalize_protocol(options)
- options[:host] = normalize_host(options)
- options[:port] = normalize_port(options)
+ result = options[:protocol]
- result << options[:protocol]
- result << rewrite_authentication(options)
- result << options[:host]
- result << ":#{options[:port]}" if options[:port]
+ if options[:user] && options[:password]
+ result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
end
+
+ result << options[:host]
+ result << ":#{options[:port]}" if options[:port]
+
result
end
@@ -88,14 +97,6 @@ module ActionDispatch
(options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil?
end
- def rewrite_authentication(options)
- if options[:user] && options[:password]
- "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
- else
- ""
- end
- end
-
def normalize_protocol(options)
case options[:protocol]
when nil
@@ -130,7 +131,7 @@ module ActionDispatch
case options[:protocol]
when "//"
- nil
+ options[:port]
when "https://"
options[:port].to_i == 443 ? nil : options[:port]
else
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 57f0963731..aa8d5a4d79 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -12,7 +12,7 @@ module ActionDispatch
@cache = nil
end
- def generate(type, name, options, recall = {}, parameterize = nil)
+ def generate(name, options, recall = {}, parameterize = nil)
constraints = recall.merge(options)
missing_keys = []
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index 430812fafe..d129ba7e16 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -1,7 +1,7 @@
#
# DO NOT MODIFY!!!!
-# This file is automatically generated by Racc 1.4.9
-# from Racc grammar file "".
+# This file is automatically generated by Racc 1.4.11
+# from Racc grammer file "".
#
require 'racc/parser.rb'
@@ -9,42 +9,38 @@ require 'racc/parser.rb'
require 'action_dispatch/journey/parser_extras'
module ActionDispatch
- module Journey # :nodoc:
- class Parser < Racc::Parser # :nodoc:
+ module Journey
+ class Parser < Racc::Parser
##### State transition tables begin ###
racc_action_table = [
- 17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
- 13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
- 14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
- 16, 8, 13, 15, 14, 7, nil, 16, 8 ]
+ 13, 15, 14, 7, 21, 16, 8, 19, 13, 15,
+ 14, 7, 17, 16, 8, 13, 15, 14, 7, 24,
+ 16, 8, 13, 15, 14, 7, 19, 16, 8 ]
racc_action_check = [
- 1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
- 20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
- 7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
- 19, 19, 0, 0, 0, 0, nil, 0, 0 ]
+ 2, 2, 2, 2, 17, 2, 2, 2, 0, 0,
+ 0, 0, 1, 0, 0, 19, 19, 19, 19, 20,
+ 19, 19, 7, 7, 7, 7, 22, 7, 7 ]
racc_action_pointer = [
- 30, 0, nil, nil, nil, nil, nil, 16, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
- 8, nil, nil, nil ]
+ 6, 12, -2, nil, nil, nil, nil, 20, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 4, nil, 13,
+ 13, nil, 17, nil, nil ]
racc_action_default = [
- -18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
- -11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
- -18, 24, -8, -7 ]
+ -19, -19, -2, -3, -4, -5, -6, -19, -10, -11,
+ -12, -13, -14, -15, -16, -17, -18, -19, -1, -19,
+ -19, 25, -8, -9, -7 ]
racc_goto_table = [
- 18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
+ 1, 22, 18, 23, nil, nil, nil, 20 ]
racc_goto_check = [
- 2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
+ 1, 2, 1, 3, nil, nil, nil, 1 ]
racc_goto_pointer = [
- nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
+ nil, 0, -18, -16, nil, nil, nil, nil, nil, nil,
nil ]
racc_goto_default = [
@@ -61,19 +57,20 @@ racc_reduce_table = [
1, 12, :_reduce_none,
3, 15, :_reduce_7,
3, 13, :_reduce_8,
- 1, 16, :_reduce_9,
+ 3, 13, :_reduce_9,
+ 1, 16, :_reduce_10,
1, 14, :_reduce_none,
1, 14, :_reduce_none,
1, 14, :_reduce_none,
1, 14, :_reduce_none,
- 1, 19, :_reduce_14,
- 1, 17, :_reduce_15,
- 1, 18, :_reduce_16,
- 1, 20, :_reduce_17 ]
+ 1, 19, :_reduce_15,
+ 1, 17, :_reduce_16,
+ 1, 18, :_reduce_17,
+ 1, 20, :_reduce_18 ]
-racc_reduce_n = 18
+racc_reduce_n = 19
-racc_shift_n = 24
+racc_shift_n = 25
racc_token_table = {
false => 0,
@@ -137,12 +134,12 @@ Racc_debug_parser = false
# reduce 0 omitted
def _reduce_1(val, _values, result)
- result = Cat.new(val.first, val.last)
+ result = Cat.new(val.first, val.last)
result
end
def _reduce_2(val, _values, result)
- result = val.first
+ result = val.first
result
end
@@ -155,21 +152,24 @@ end
# reduce 6 omitted
def _reduce_7(val, _values, result)
- result = Group.new(val[1])
+ result = Group.new(val[1])
result
end
def _reduce_8(val, _values, result)
- result = Or.new([val.first, val.last])
+ result = Or.new([val.first, val.last])
result
end
def _reduce_9(val, _values, result)
- result = Star.new(Symbol.new(val.last))
+ result = Or.new([val.first, val.last])
result
end
-# reduce 10 omitted
+def _reduce_10(val, _values, result)
+ result = Star.new(Symbol.new(val.last))
+ result
+end
# reduce 11 omitted
@@ -177,23 +177,25 @@ end
# reduce 13 omitted
-def _reduce_14(val, _values, result)
- result = Slash.new('/')
- result
-end
+# reduce 14 omitted
def _reduce_15(val, _values, result)
- result = Symbol.new(val.first)
+ result = Slash.new('/')
result
end
def _reduce_16(val, _values, result)
- result = Literal.new(val.first)
+ result = Symbol.new(val.first)
result
end
def _reduce_17(val, _values, result)
- result = Dot.new(val.first)
+ result = Literal.new(val.first)
+ result
+end
+
+def _reduce_18(val, _values, result)
+ result = Dot.new(val.first)
result
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
index 040f8d5922..0ead222551 100644
--- a/actionpack/lib/action_dispatch/journey/parser.y
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -4,7 +4,7 @@ token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
rule
expressions
- : expressions expression { result = Cat.new(val.first, val.last) }
+ : expression expressions { result = Cat.new(val.first, val.last) }
| expression { result = val.first }
| or
;
@@ -17,7 +17,8 @@ rule
: LPAREN expressions RPAREN { result = Group.new(val[1]) }
;
or
- : expressions OR expression { result = Or.new([val.first, val.last]) }
+ : expression OR expression { result = Or.new([val.first, val.last]) }
+ | expression OR or { result = Or.new([val.first, val.last]) }
;
star
: STAR { result = Star.new(Symbol.new(val.last)) }
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index fb155e516f..28c75618de 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -28,6 +28,11 @@ module ActionDispatch
@required_names = nil
@re = nil
@offsets = nil
+ @format = Visitors::FormatBuilder.new.accept(spec)
+ end
+
+ def format_path(path_options)
+ @format.evaluate path_options
end
def ast
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index c8eb0f6f2d..d4df96314f 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -76,7 +76,7 @@ module ActionDispatch
value.to_s == defaults[key].to_s && !required_parts.include?(key)
end
- Visitors::Formatter.new(path_options).accept(path.spec)
+ path.format_path path_options
end
def optimized_path
@@ -101,6 +101,10 @@ module ActionDispatch
end
end
+ def glob?
+ !path.spec.grep(Nodes::Star).empty?
+ end
+
def dispatcher?
@dispatcher
end
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index d1a004af50..ac4ecb1e65 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -1,5 +1,3 @@
-require 'uri'
-
module ActionDispatch
module Journey # :nodoc:
class Router # :nodoc:
@@ -25,31 +23,67 @@ module ActionDispatch
# URI path and fragment escaping
# http://tools.ietf.org/html/rfc3986
- module UriEscape # :nodoc:
- # Symbol captures can generate multiple path segments, so include /.
- reserved_segment = '/'
- reserved_fragment = '/?'
- reserved_pchar = ':@&=+$,;%'
-
- safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
- safe_segment = "#{safe_pchar}#{reserved_segment}"
- safe_fragment = "#{safe_pchar}#{reserved_fragment}"
- UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
- UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
+ class UriEncoder # :nodoc:
+ ENCODE = "%%%02X".freeze
+ ENCODING = Encoding::US_ASCII
+ EMPTY = "".force_encoding(ENCODING).freeze
+ DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(ENCODING) }
+
+ ALPHA = "a-zA-Z".freeze
+ DIGIT = "0-9".freeze
+ UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze
+ SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze
+
+ ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
+
+ FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
+ SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
+ PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
+
+ def escape_fragment(fragment)
+ escape(fragment, FRAGMENT)
+ end
+
+ def escape_path(path)
+ escape(path, PATH)
+ end
+
+ def escape_segment(segment)
+ escape(segment, SEGMENT)
+ end
+
+ def unescape_uri(uri)
+ uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(uri.encoding)
+ end
+
+ protected
+ def escape(component, pattern)
+ component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(ENCODING)
+ end
+
+ def percent_encode(unsafe)
+ safe = EMPTY.dup
+ unsafe.each_byte { |b| safe << DEC2HEX[b] }
+ safe
+ end
end
- Parser = URI::Parser.new
+ ENCODER = UriEncoder.new
def self.escape_path(path)
- Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
+ ENCODER.escape_path(path.to_s)
+ end
+
+ def self.escape_segment(segment)
+ ENCODER.escape_segment(segment.to_s)
end
def self.escape_fragment(fragment)
- Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
+ ENCODER.escape_fragment(fragment.to_s)
end
def self.unescape_uri(uri)
- Parser.unescape(uri)
+ ENCODER.unescape_uri(uri)
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index daade5bb74..b635576b6a 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -1,14 +1,56 @@
# encoding: utf-8
-require 'thread_safe'
-
module ActionDispatch
module Journey # :nodoc:
+ class Format
+ ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
+ ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
+
+ class Parameter < Struct.new(:name, :escaper)
+ def escape(value); escaper.call value; end
+ end
+
+ def self.required_path(symbol)
+ Parameter.new symbol, ESCAPE_PATH
+ end
+
+ def self.required_segment(symbol)
+ Parameter.new symbol, ESCAPE_SEGMENT
+ end
+
+ def initialize(parts)
+ @parts = parts
+ @children = []
+ @parameters = []
+
+ parts.each_with_index do |object,i|
+ case object
+ when Journey::Format
+ @children << i
+ when Parameter
+ @parameters << i
+ end
+ end
+ end
+
+ def evaluate(hash)
+ parts = @parts.dup
+
+ @parameters.each do |index|
+ param = parts[index]
+ value = hash.fetch(param.name) { return ''.freeze }
+ parts[index] = param.escape value
+ end
+
+ @children.each { |index| parts[index] = parts[index].evaluate(hash) }
+
+ parts.join
+ end
+ end
+
module Visitors # :nodoc:
class Visitor # :nodoc:
- DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k|
- h[k] = :"visit_#{k}"
- }
+ DISPATCH_CACHE = {}
def accept(node)
visit(node)
@@ -38,11 +80,41 @@ module ActionDispatch
def visit_STAR(n); unary(n); end
def terminal(node); end
- %w{ LITERAL SYMBOL SLASH DOT }.each do |t|
- class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
+ def visit_LITERAL(n); terminal(n); end
+ def visit_SYMBOL(n); terminal(n); end
+ def visit_SLASH(n); terminal(n); end
+ def visit_DOT(n); terminal(n); end
+
+ private_instance_methods(false).each do |pim|
+ next unless pim =~ /^visit_(.*)$/
+ DISPATCH_CACHE[$1.to_sym] = pim
end
end
+ class FormatBuilder < Visitor # :nodoc:
+ def accept(node); Journey::Format.new(super); end
+ def terminal(node); [node.left]; end
+
+ def binary(node)
+ visit(node.left) + visit(node.right)
+ end
+
+ def visit_GROUP(n); [Journey::Format.new(unary(n))]; end
+
+ def visit_STAR(n)
+ [Journey::Format.required_path(n.left.to_sym)]
+ end
+
+ def visit_SYMBOL(n)
+ symbol = n.to_sym
+ if symbol == :controller
+ [Journey::Format.required_path(symbol)]
+ else
+ [Journey::Format.required_segment(symbol)]
+ end
+ end
+ end
+
# Loop through the requirements AST
class Each < Visitor # :nodoc:
attr_reader :block
@@ -52,8 +124,8 @@ module ActionDispatch
end
def visit(node)
- super
block.call(node)
+ super
end
end
@@ -105,49 +177,6 @@ module ActionDispatch
end
end
- # Used for formatting urls (url_for)
- class Formatter < Visitor # :nodoc:
- attr_reader :options
-
- def initialize(options)
- @options = options
- end
-
- private
-
- def visit(node, optional = false)
- case node.type
- when :LITERAL, :SLASH, :DOT
- node.left
- when :STAR
- visit(node.left)
- when :GROUP
- visit(node.left, true)
- when :CAT
- visit_CAT(node, optional)
- when :SYMBOL
- visit_SYMBOL(node)
- end
- end
-
- def visit_CAT(node, optional)
- left = visit(node.left, optional)
- right = visit(node.right, optional)
-
- if optional && !(right && left)
- ""
- else
- [left, right].join
- end
- end
-
- def visit_SYMBOL(node)
- if value = options[node.to_sym]
- Router::Utils.escape_path(value)
- end
- end
- end
-
class Dot < Visitor # :nodoc:
def initialize
@nodes = []
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index c0039fa3f5..22b16b628d 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -176,11 +176,11 @@ module ActionDispatch
module VerifyAndUpgradeLegacySignedMessage
def initialize(*args)
super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token])
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(@options[:secret_token], serializer: NullSerializer)
end
def verify_and_upgrade_legacy_signed_message(name, signed_message)
- @legacy_verifier.verify(signed_message).tap do |value|
+ deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value|
self[name] = { value: value }
end
rescue ActiveSupport::MessageVerifier::InvalidSignature
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index cbb2d475b1..6c8944e067 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -32,9 +32,8 @@ module ActionDispatch
end
def render_html(status)
- found = false
- path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
if found || File.exist?(path)
render_format(status, 'text/html', File.read(path))
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index c1df518b14..cbb066b092 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -31,7 +31,7 @@ module ActionDispatch
TRUSTED_PROXIES = %r{
^127\.0\.0\.1$ | # localhost IPv4
^::1$ | # localhost IPv6
- ^fc00: | # private IPv6 range fc00
+ ^[fF][cCdD] | # private IPv6 range fc00::/7
^10\. | # private IPv4 range 10.x.x.x
^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
^192\.168\. # private IPv4 range 192.168.x.x
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 3be0ce1860..0864e7ef2a 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -29,7 +29,7 @@ module ActionDispatch
#
# Configure your session store in config/initializers/session_store.rb:
#
- # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
+ # Rails.application.config.session_store :cookie_store, key: '_your_app_session'
#
# Configure your secret key in config/secrets.yml:
#
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 999c022535..0c7caef25d 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -32,11 +32,14 @@ module ActionDispatch
private
def redirect_to_https(request)
- url = URI(request.url)
- url.scheme = "https"
- url.host = @host if @host
- url.port = @port if @port
- headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s }
+ host = @host || request.host
+ port = @port || request.port
+
+ location = "https://#{host}"
+ location << ":#{port}" if port != 80
+ location << request.fullpath
+
+ headers = { 'Content-Type' => 'text/html', 'Location' => location }
[301, headers, []]
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
index f154021ae6..f154021ae6 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
new file mode 100644
index 0000000000..603de54b8b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
@@ -0,0 +1,9 @@
+<%= @exception.class.to_s %><%
+ if @request.parameters['controller']
+%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+<% end %>
+
+<%= @exception.message %>
+<%= render template: "rescues/_source" %>
+<%= render template: "rescues/_trace" %>
+<%= render template: "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 323873ba4b..cce0d75af4 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -4,21 +4,41 @@
border-collapse: collapse;
}
- #route_table td {
- padding: 0 30px;
+ #route_table thead tr {
+ border-bottom: 2px solid #ddd;
+ }
+
+ #route_table thead tr.bottom {
+ border-bottom: none;
}
- #route_table tr.bottom th {
- padding-bottom: 10px;
+ #route_table thead tr.bottom th {
+ padding: 10px 0;
line-height: 15px;
}
- #route_table .matched_paths {
+ #route_table tbody tr {
+ border-bottom: 1px solid #ddd;
+ }
+
+ #route_table tbody tr:nth-child(odd) {
+ background: #f2f2f2;
+ }
+
+ #route_table tbody.exact_matches,
+ #route_table tbody.fuzzy_matches {
background-color: LightGoldenRodYellow;
+ border-bottom: solid 2px SlateGrey;
}
- #route_table .matched_paths {
- border-bottom: solid 3px SlateGrey;
+ #route_table tbody.exact_matches tr,
+ #route_table tbody.fuzzy_matches tr {
+ background: none;
+ border-bottom: none;
+ }
+
+ #route_table td {
+ padding: 4px 30px;
}
#path_search {
@@ -45,13 +65,15 @@
<th><%# HTTP Verb %>
</th>
<th><%# Path %>
- <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %>
+ <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %>
</th>
<th><%# Controller#action %>
</th>
</tr>
</thead>
- <tbody class='matched_paths' id='matched_paths'>
+ <tbody class='exact_matches' id='exact_matches'>
+ </tbody>
+ <tbody class='fuzzy_matches' id='fuzzy_matches'>
</tbody>
<tbody>
<%= yield %>
@@ -59,6 +81,7 @@
</table>
<script type='text/javascript'>
+ // Iterates each element through a function
function each(elems, func) {
if (!elems instanceof Array) { elems = [elems]; }
for (var i = 0, len = elems.length; i < len; i++) {
@@ -66,77 +89,110 @@
}
}
- function setValOn(elems, val) {
- each(elems, function(elem) {
- elem.innerHTML = val;
- });
+ // Sets innerHTML for an element
+ function setContent(elem, text) {
+ elem.innerHTML = text;
}
- function onClick(elems, func) {
- each(elems, function(elem) {
- elem.onclick = func;
- });
- }
+ // Enables path search functionality
+ function setupMatchPaths() {
+ // Check if the user input (sanitized as a path) matches the regexp data attribute
+ function checkExactMatch(section, elem, value) {
+ var string = sanitizePath(value),
+ regexp = elem.getAttribute("data-regexp");
- // Enables functionality to toggle between `_path` and `_url` helper suffixes
- function setupRouteToggleHelperLinks() {
- var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
- onClick(toggleLinks, function(){
- var helperTxt = this.getAttribute("data-route-helper"),
- helperElems = document.querySelectorAll('[data-route-name] span.helper');
- setValOn(helperElems, helperTxt);
- });
- }
+ showMatch(string, regexp, section, elem);
+ }
- // takes an array of elements with a data-regexp attribute and
- // passes their parent <tr> into the callback function
- // if the regexp matches a given path
- function eachElemsForPath(elems, path, func) {
- each(elems, function(e){
- var reg = e.getAttribute("data-regexp");
- if (path.match(RegExp(reg))) {
- func(e.parentNode.cloneNode(true));
- }
- })
- }
+ // Check if the route path data attribute contains the user input
+ function checkFuzzyMatch(section, elem, value) {
+ var string = elem.getAttribute("data-route-path"),
+ regexp = value;
- // Ensure path always starts with a slash "/" and remove params or fragments
- function sanitizePath(path) {
- var path = path.charAt(0) == '/' ? path : "/" + path;
- return path.replace(/\#.*|\?.*/, '');
- }
+ showMatch(string, regexp, section, elem);
+ }
- // Enables path search functionality
- function setupMatchPaths() {
- var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
- pathElem = document.querySelector('#path_search'),
- selectedSection = document.querySelector('#matched_paths'),
- noMatchText = '<tr><th colspan="4">None</th></tr>';
+ // Display the parent <tr> element in the appropriate section when there's a match
+ function showMatch(string, regexp, section, elem) {
+ if(string.match(RegExp(regexp))) {
+ section.appendChild(elem.parentNode.cloneNode(true));
+ }
+ }
+
+ // Check if there are any matched results in a section
+ function checkNoMatch(section, defaultText, noMatchText) {
+ if (section.innerHTML === defaultText) {
+ setContent(section, defaultText + noMatchText);
+ }
+ }
+ // Ensure path always starts with a slash "/" and remove params or fragments
+ function sanitizePath(path) {
+ var path = path.charAt(0) == '/' ? path : "/" + path;
+ return path.replace(/\#.*|\?.*/, '');
+ }
- // Remove matches if no path is present
- pathElem.onblur = function(e) {
- if (pathElem.value === "") selectedSection.innerHTML = "";
+ var regexpElems = document.querySelectorAll('#route_table [data-regexp]'),
+ searchElem = document.querySelector('#search'),
+ exactMatches = document.querySelector('#exact_matches'),
+ fuzzyMatches = document.querySelector('#fuzzy_matches');
+
+ // Remove matches when no search value is present
+ searchElem.onblur = function(e) {
+ if (searchElem.value === "") {
+ setContent(exactMatches, "");
+ setContent(fuzzyMatches, "");
+ }
}
// On key press perform a search for matching paths
- pathElem.onkeyup = function(e){
- var path = sanitizePath(pathElem.value),
- defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>';
+ searchElem.onkeyup = function(e){
+ var userInput = searchElem.value,
+ defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + sanitizePath(userInput) +'):</th></tr>',
+ defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + userInput +'):</th></tr>',
+ noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>',
+ noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>';
// Clear out results section
- selectedSection.innerHTML= defaultText;
+ setContent(exactMatches, defaultExactMatch);
+ setContent(fuzzyMatches, defaultFuzzyMatch);
+
+ // Display exact matches and fuzzy matches
+ each(regexpElems, function(elem) {
+ checkExactMatch(exactMatches, elem, userInput);
+ checkFuzzyMatch(fuzzyMatches, elem, userInput);
+ })
+
+ // Display 'No Matches' message when no matches are found
+ checkNoMatch(exactMatches, defaultExactMatch, noExactMatch);
+ checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch);
+ }
+ }
- // Display matches if they exist
- eachElemsForPath(regexpElems, path, function(e){
- selectedSection.appendChild(e);
+ // Enables functionality to toggle between `_path` and `_url` helper suffixes
+ function setupRouteToggleHelperLinks() {
+
+ // Sets content for each element
+ function setValOn(elems, val) {
+ each(elems, function(elem) {
+ setContent(elem, val);
});
+ }
- // If no match present, tell the user
- if (selectedSection.innerHTML === defaultText) {
- selectedSection.innerHTML = selectedSection.innerHTML + noMatchText;
- }
+ // Sets onClick event for each element
+ function onClick(elems, func) {
+ each(elems, function(elem) {
+ elem.onclick = func;
+ });
}
+
+ var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
+ onClick(toggleLinks, function(){
+ var helperTxt = this.getAttribute("data-route-helper"),
+ helperElems = document.querySelectorAll('[data-route-name] span.helper');
+
+ setValOn(helperElems, helperTxt);
+ });
}
setupMatchPaths();
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index a9ac2bce1d..ce03164ca9 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,6 +1,7 @@
# encoding: UTF-8
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
+require 'active_support/dependencies/autoload'
module ActionDispatch
# The routing module provides URL rewriting in native Ruby. It's a way to
@@ -11,7 +12,7 @@ module ActionDispatch
# Think of creating routes as drawing a map for your requests. The map tells
# them where to go based on some predefined pattern:
#
- # AppName::Application.routes.draw do
+ # Rails.application.routes.draw do
# Pattern 1 tells some request to go to one place
# Pattern 2 tell them to go to another
# ...
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 6f0b49cf28..58c7f5330e 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -159,6 +159,8 @@ module ActionDispatch
@defaults[key] ||= default
end
end
+ elsif options[:constraints]
+ verify_callable_constraint(options[:constraints])
end
if Regexp === options[:format]
@@ -168,6 +170,12 @@ module ActionDispatch
end
end
+ def verify_callable_constraint(callable_constraint)
+ unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
+ raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
+ end
+ end
+
def normalize_conditions!
@conditions[:path_info] = path
@@ -340,18 +348,34 @@ module ActionDispatch
match '/', { :as => :root, :via => :get }.merge!(options)
end
- # Matches a url pattern to one or more routes. Any symbols in a pattern
- # are interpreted as url query parameters and thus available as +params+
- # in an action:
+ # Matches a url pattern to one or more routes.
+ #
+ # You should not use the `match` method in your router
+ # without specifying an HTTP method.
+ #
+ # If you want to expose your action to both GET and POST, use:
#
# # sets :controller, :action and :id in params
- # match ':controller/:action/:id'
+ # match ':controller/:action/:id', via: [:get, :post]
+ #
+ # Note that +:controller+, +:action+ and +:id+ are interpreted as url
+ # query parameters and thus available through +params+ in an action.
+ #
+ # If you want to expose your action to GET, use `get` in the router:
+ #
+ # Instead of:
+ #
+ # match ":controller/:action/:id"
+ #
+ # Do:
+ #
+ # get ":controller/:action/:id"
#
# Two of these symbols are special, +:controller+ maps to the controller
# and +:action+ to the controller's action. A pattern can also map
# wildcard segments (globs) to params:
#
- # match 'songs/*category/:title', to: 'songs#show'
+ # get 'songs/*category/:title', to: 'songs#show'
#
# # 'songs/rock/classic/stairway-to-heaven' sets
# # params[:category] = 'rock/classic'
@@ -364,17 +388,17 @@ module ActionDispatch
# When a pattern points to an internal route, the route's +:action+ and
# +:controller+ should be set in options or hash shorthand. Examples:
#
- # match 'photos/:id' => 'photos#show'
- # match 'photos/:id', to: 'photos#show'
- # match 'photos/:id', controller: 'photos', action: 'show'
+ # match 'photos/:id' => 'photos#show', via: :get
+ # match 'photos/:id', to: 'photos#show', via: :get
+ # match 'photos/:id', controller: 'photos', action: 'show', via: :get
#
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
- # match 'photos/:id', to: PhotoRackApp
+ # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
+ # match 'photos/:id', to: PhotoRackApp, via: :get
# # Yes, controller actions are just rack endpoints
- # match 'photos/:id', to: PhotosController.action(:show)
+ # match 'photos/:id', to: PhotosController.action(:show), via: :get
#
# Because requesting various HTTP verbs with a single action has security
# implications, you must either specify the actions in
@@ -397,7 +421,7 @@ module ActionDispatch
# [:module]
# The namespace for :controller.
#
- # match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
+ # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
# # => Sekret::PostsController
#
# See <tt>Scoping#namespace</tt> for its scope equivalent.
@@ -416,9 +440,9 @@ module ActionDispatch
# Points to a +Rack+ endpoint. Can be an object that responds to
# +call+ or a string representing a controller's action.
#
- # match 'path', to: 'controller#action'
- # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
- # match 'path', to: RackApp
+ # match 'path', to: 'controller#action', via: :get
+ # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
+ # match 'path', to: RackApp, via: :get
#
# [:on]
# Shorthand for wrapping routes in a specific RESTful context. Valid
@@ -443,14 +467,14 @@ module ActionDispatch
# other than path can also be specified with any object
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
#
- # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
+ # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
#
- # match 'json_only', constraints: { format: 'json' }
+ # match 'json_only', constraints: { format: 'json' }, via: :get
#
# class Whitelist
# def matches?(request) request.remote_ip == '1.2.3.4' end
# end
- # match 'path', to: 'c#a', constraints: Whitelist.new
+ # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
#
# See <tt>Scoping#constraints</tt> for more examples with its scope
# equivalent.
@@ -459,7 +483,7 @@ module ActionDispatch
# Sets defaults for parameters
#
# # Sets params[:format] to 'jpg' by default
- # match 'path', to: 'c#a', defaults: { format: 'jpg' }
+ # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
#
@@ -468,7 +492,7 @@ module ActionDispatch
# false, the pattern matches any request prefixed with the given path.
#
# # Matches any request starting with 'path'
- # match 'path', to: 'c#a', anchor: false
+ # match 'path', to: 'c#a', anchor: false, via: :get
#
# [:format]
# Allows you to specify the default value for optional +format+
@@ -554,18 +578,17 @@ module ActionDispatch
_route = @set.named_routes.routes[name.to_sym]
_routes = @set
app.routes.define_mounted_helper(name)
- app.routes.singleton_class.class_eval do
- redefine_method :mounted? do
- true
- end
-
- redefine_method :_generate_prefix do |options|
+ app.routes.extend Module.new {
+ def mounted?; true; end
+ define_method :find_script_name do |options|
+ super(options) || begin
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
_route.segment_keys.each { |k| options.delete(k) }
_routes.url_helpers.send("#{name}_path", prefix_options)
+ end
end
- end
+ }
end
end
@@ -995,6 +1018,7 @@ module ActionDispatch
@as = options[:as]
@param = (options[:param] || :id).to_sym
@options = options
+ @shallow = false
end
def default_actions
@@ -1055,6 +1079,13 @@ module ActionDispatch
"#{path}/:#{nested_param}"
end
+ def shallow=(value)
+ @shallow = value
+ end
+
+ def shallow?
+ @shallow
+ end
end
class SingletonResource < Resource #:nodoc:
@@ -1361,7 +1392,7 @@ module ActionDispatch
end
with_scope_level(:nested) do
- if shallow? && nesting_depth > 1
+ if shallow? && shallow_nesting_depth > 1
shallow_scope(parent_resource.nested_scope, nested_options) { yield }
else
scope(parent_resource.nested_scope, nested_options) { yield }
@@ -1576,6 +1607,7 @@ module ActionDispatch
end
def resource_scope(kind, resource) #:nodoc:
+ resource.shallow = @scope[:shallow]
old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
@nesting.push(resource)
@@ -1600,6 +1632,10 @@ module ActionDispatch
@nesting.size
end
+ def shallow_nesting_depth #:nodoc:
+ @nesting.select(&:shallow?).size
+ end
+
def param_constraint? #:nodoc:
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 2fb03f2712..bd3696cda1 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -101,53 +101,45 @@ module ActionDispatch
# polymorphic_url(Comment) # same as comments_url()
#
def polymorphic_url(record_or_hash_or_array, options = {})
- if record_or_hash_or_array.kind_of?(Array)
- record_or_hash_or_array = record_or_hash_or_array.compact
- if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
- proxy = record_or_hash_or_array.shift
- end
- record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
- end
-
- record = extract_record(record_or_hash_or_array)
- record = convert_to_model(record)
-
- args = Array === record_or_hash_or_array ?
- record_or_hash_or_array.dup :
- [ record_or_hash_or_array ]
-
- inflection = if options[:action] && options[:action].to_s == "new"
- args.pop
- :singular
- elsif (record.respond_to?(:persisted?) && !record.persisted?)
- args.pop
- :plural
- elsif record.is_a?(Class)
- args.pop
- :plural
- else
- :singular
+ if Hash === record_or_hash_or_array
+ options = record_or_hash_or_array.merge(options)
+ record = options.delete :id
+ return polymorphic_url record, options
end
- args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
- named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
-
- url_options = options.except(:action, :routing_type)
- unless url_options.empty?
- args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
- end
+ opts = options.dup
+ action = opts.delete :action
+ type = opts.delete(:routing_type) || :url
- args.collect! { |a| convert_to_model(a) }
+ HelperMethodBuilder.polymorphic_method self,
+ record_or_hash_or_array,
+ action,
+ type,
+ opts
- (proxy || self).send(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
# <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
def polymorphic_path(record_or_hash_or_array, options = {})
- polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
+ if Hash === record_or_hash_or_array
+ options = record_or_hash_or_array.merge(options)
+ record = options.delete :id
+ return polymorphic_path record, options
+ end
+
+ opts = options.dup
+ action = opts.delete :action
+ type = :path
+
+ HelperMethodBuilder.polymorphic_method self,
+ record_or_hash_or_array,
+ action,
+ type,
+ opts
end
+
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__ + 1
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
@@ -165,54 +157,169 @@ module ActionDispatch
end
private
- def action_prefix(options)
- options[:action] ? "#{options[:action]}_" : ''
+
+ class HelperMethodBuilder # :nodoc:
+ CACHE = { 'path' => {}, 'url' => {} }
+
+ def self.get(action, type)
+ type = type.to_s
+ CACHE[type].fetch(action) { build action, type }
+ end
+
+ def self.url; CACHE['url'.freeze][nil]; end
+ def self.path; CACHE['path'.freeze][nil]; end
+
+ def self.build(action, type)
+ prefix = action ? "#{action}_" : ""
+ suffix = type
+ if action.to_s == 'new'
+ HelperMethodBuilder.singular prefix, suffix
+ else
+ HelperMethodBuilder.plural prefix, suffix
+ end
+ end
+
+ def self.singular(prefix, suffix)
+ new(->(name) { name.singular_route_key }, prefix, suffix)
end
- def routing_type(options)
- options[:routing_type] || :url
+ def self.plural(prefix, suffix)
+ new(->(name) { name.route_key }, prefix, suffix)
end
- def build_named_route_call(records, inflection, options = {})
- if records.is_a?(Array)
- record = records.pop
- route = records.map do |parent|
- if parent.is_a?(Symbol) || parent.is_a?(String)
- parent
- else
- model_name_from_record_or_class(parent).singular_route_key
- end
+ def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
+ builder = get action, type
+
+ case record_or_hash_or_array
+ when Array
+ if record_or_hash_or_array.empty? || record_or_hash_or_array.include?(nil)
+ raise ArgumentError, "Nil location provided. Can't build URI."
end
+ if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
+ recipient = record_or_hash_or_array.shift
+ end
+
+ method, args = builder.handle_list record_or_hash_or_array
+ when String, Symbol
+ method, args = builder.handle_string record_or_hash_or_array
+ when Class
+ method, args = builder.handle_class record_or_hash_or_array
+
+ when nil
+ raise ArgumentError, "Nil location provided. Can't build URI."
+ else
+ method, args = builder.handle_model record_or_hash_or_array
+ end
+
+
+ if options.empty?
+ recipient.send(method, *args)
else
- record = extract_record(records)
- route = []
+ recipient.send(method, *args, options)
end
+ end
+
+ attr_reader :suffix, :prefix
- if record.is_a?(Symbol) || record.is_a?(String)
- route << record
- elsif record
- if inflection == :singular
- route << model_name_from_record_or_class(record).singular_route_key
+ def initialize(key_strategy, prefix, suffix)
+ @key_strategy = key_strategy
+ @prefix = prefix
+ @suffix = suffix
+ end
+
+ def handle_string(record)
+ [get_method_for_string(record), []]
+ end
+
+ def handle_string_call(target, str)
+ target.send get_method_for_string str
+ end
+
+ def handle_class(klass)
+ [get_method_for_class(klass), []]
+ end
+
+ def handle_class_call(target, klass)
+ target.send get_method_for_class klass
+ end
+
+ def handle_model(record)
+ args = []
+
+ model = record.to_model
+ name = if record.persisted?
+ args << model
+ model.class.model_name.singular_route_key
+ else
+ @key_strategy.call model.class.model_name
+ end
+
+ named_route = prefix + "#{name}_#{suffix}"
+
+ [named_route, args]
+ end
+
+ def handle_model_call(target, model)
+ method, args = handle_model model
+ target.send(method, *args)
+ end
+
+ def handle_list(list)
+ record_list = list.dup
+ record = record_list.pop
+
+ args = []
+
+ route = record_list.map { |parent|
+ case parent
+ when Symbol, String
+ parent.to_s
+ when Class
+ args << parent
+ parent.model_name.singular_route_key
else
- route << model_name_from_record_or_class(record).route_key
+ args << parent.to_model
+ parent.to_model.class.model_name.singular_route_key
end
+ }
+
+ route <<
+ case record
+ when Symbol, String
+ record.to_s
+ when Class
+ @key_strategy.call record.model_name
else
- raise ArgumentError, "Nil location provided. Can't build URI."
+ if record.persisted?
+ args << record.to_model
+ record.to_model.class.model_name.singular_route_key
+ else
+ @key_strategy.call record.to_model.class.model_name
+ end
end
- route << routing_type(options)
+ route << suffix
- action_prefix(options) + route.join("_")
+ named_route = prefix + route.join("_")
+ [named_route, args]
end
- def extract_record(record_or_hash_or_array)
- case record_or_hash_or_array
- when Array; record_or_hash_or_array.last
- when Hash; record_or_hash_or_array[:id]
- else record_or_hash_or_array
- end
+ private
+
+ def get_method_for_class(klass)
+ name = @key_strategy.call klass.model_name
+ prefix + "#{name}_#{suffix}"
+ end
+
+ def get_method_for_string(str)
+ prefix + "#{str}_#{suffix}"
end
+
+ [nil, 'new', 'edit'].each do |action|
+ CACHE['url'][action] = build action, 'url'
+ CACHE['path'][action] = build action, 'path'
+ end
+ end
end
end
end
-
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index a03fb4cee7..01378ae0ec 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,11 +1,13 @@
require 'action_dispatch/journey'
require 'forwardable'
require 'thread_safe'
+require 'active_support/concern'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/array/extract_options'
require 'action_controller/metal/exceptions'
+require 'action_dispatch/http/request'
module ActionDispatch
module Routing
@@ -155,7 +157,7 @@ module ActionDispatch
end
def self.optimize_helper?(route)
- route.requirements.except(:controller, :action).empty?
+ !route.glob? && route.path.requirements.empty?
end
class OptimizedUrlHelper < UrlHelper # :nodoc:
@@ -171,8 +173,7 @@ module ActionDispatch
def call(t, args)
if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
- options = @options.dup
- options.merge!(t.url_options) if t.respond_to?(:url_options)
+ options = t.url_options.merge @options
options[:path] = optimized_helper(args)
ActionDispatch::Http::URL.url_for(options)
else
@@ -194,7 +195,7 @@ module ActionDispatch
end
def replace_segment(params, segment)
- Symbol === segment ? @klass.escape_fragment(params[segment]) : segment
+ Symbol === segment ? @klass.escape_segment(params[segment]) : segment
end
def optimize_routes_generation?(t)
@@ -225,20 +226,23 @@ module ActionDispatch
end
def call(t, args)
- t.url_for(handle_positional_args(t, args, @options, @segment_keys))
+ controller_options = t.url_options
+ options = controller_options.merge @options
+ hash = handle_positional_args(controller_options, args, options, @segment_keys)
+ t._routes.url_for(hash)
end
- def handle_positional_args(t, args, options, keys)
+ def handle_positional_args(controller_options, args, result, path_params)
inner_options = args.extract_options!
- result = options.dup
if args.size > 0
- if args.size < keys.size - 1 # take format into account
- keys -= t.url_options.keys if t.respond_to?(:url_options)
- keys -= options.keys
+ if args.size < path_params.size - 1 # take format into account
+ path_params -= controller_options.keys
+ path_params -= result.keys
end
- keys -= inner_options.keys
- result.merge!(Hash[keys.zip(args)])
+ path_params.each { |param|
+ result[param] = inner_options[param] || args.shift
+ }
end
result.merge!(inner_options)
@@ -393,6 +397,8 @@ module ActionDispatch
@_routes = routes
class << self
delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
+ attr_reader :_routes
+ def url_options; {}; end
end
# Make named_routes available in the module singleton
@@ -594,7 +600,7 @@ module ActionDispatch
# Generates a path from routes, returns [path, params].
# If no route is generated the formatter will raise ActionController::UrlGenerationError
def generate
- @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
+ @set.formatter.generate(named_route, options, recall, PARAMETERIZE)
end
def different_controller?
@@ -639,37 +645,46 @@ module ActionDispatch
!mounted? && default_url_options.empty?
end
- def _generate_prefix(options = {})
- nil
+ def find_script_name(options)
+ options.delete :script_name
end
- # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
+ # The +options+ argument must be a hash whose keys are *symbols*.
def url_for(options)
- options = default_url_options.merge(options || {})
+ options = default_url_options.merge options
+
+ user = password = nil
+
+ if options[:user] && options[:password]
+ user = options.delete :user
+ password = options.delete :password
+ end
- user, password = extract_authentication(options)
- recall = options.delete(:_recall)
+ recall = options.delete(:_recall) { {} }
- original_script_name = options.delete(:original_script_name).presence
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
+ original_script_name = options.delete(:original_script_name)
+ script_name = find_script_name options
if script_name && original_script_name
script_name = original_script_name + script_name
end
- path_options = options.except(*RESERVED_OPTIONS)
- path_options = yield(path_options) if block_given?
+ path_options = options.dup
+ RESERVED_OPTIONS.each { |ro| path_options.delete ro }
+
+ path, params = generate(path_options, recall)
+
+ if options.key? :params
+ params.merge! options[:params]
+ end
- path, params = generate(path_options, recall || {})
- params.merge!(options[:params] || {})
+ options[:path] = path
+ options[:script_name] = script_name
+ options[:params] = params
+ options[:user] = user
+ options[:password] = password
- ActionDispatch::Http::URL.url_for(options.merge!({
- :path => path,
- :script_name => script_name,
- :params => params,
- :user => user,
- :password => password
- }))
+ ActionDispatch::Http::URL.url_for(options)
end
def call(env)
@@ -715,17 +730,6 @@ module ActionDispatch
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
-
- private
-
- def extract_authentication(options)
- if options[:user] && options[:password]
- [options.delete(:user), options.delete(:password)]
- else
- nil
- end
- end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 4a0ef40873..e624fe3c4a 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -155,10 +155,14 @@ module ActionDispatch
_routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
when String
options
+ when Symbol
+ HelperMethodBuilder.url.handle_string_call self, options
when Array
polymorphic_url(options, options.extract_options!)
+ when Class
+ HelperMethodBuilder.url.handle_class_call self, options
else
- polymorphic_url(options)
+ HelperMethodBuilder.url.handle_model_call self, options
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 68feb26936..0adc6c84ff 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -73,7 +73,13 @@ module ActionDispatch
if Regexp === fragment
fragment
else
- @controller._compute_redirect_to_location(fragment)
+ handle = @controller || Class.new(ActionController::Metal) do
+ include ActionController::Redirecting
+ def initialize(request)
+ @_request = request
+ end
+ end.new(@request)
+ handle._compute_redirect_to_location(fragment)
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 8a128427bf..12023e6f77 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -267,7 +267,7 @@ module ActionDispatch
text.strip! unless NO_STRIP.include?(match.name)
text.sub!(/\A\n/, '') if match.name == "textarea"
unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
- content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text)
+ content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, text)
true
end
end
@@ -276,7 +276,7 @@ module ActionDispatch
html = match.children.map(&:to_s).join
html.strip! unless NO_STRIP.include?(match.name)
unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
- content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, html)
+ content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, html)
true
end
end
@@ -289,7 +289,7 @@ module ActionDispatch
# FIXME: minitest provides messaging when we use assert_operator,
# so is this custom message really needed?
- message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.)
+ message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size})
if count
assert_equal count, matches.size, message
else
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index b1a5044399..fc59bf19c4 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -24,15 +24,21 @@ module AbstractController
test "does not respond to unknown mime types" do
collector = MyCollector.new
- assert !collector.respond_to?(:unknown)
+ assert_not_respond_to collector, :unknown
end
test "register mime types on method missing" do
AbstractController::Collector.send(:remove_method, :js)
- collector = MyCollector.new
- assert !collector.respond_to?(:js)
- collector.js
- assert_respond_to collector, :js
+ begin
+ collector = MyCollector.new
+ assert_not_respond_to collector, :js
+ collector.js
+ assert_respond_to collector, :js
+ ensure
+ unless AbstractController::Collector.method_defined? :js
+ AbstractController::Collector.generate_method_for_mime :js
+ end
+ end
end
test "does not register unknown mime types" do
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 03a4741f42..46de36317e 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -320,8 +320,8 @@ module ActionDispatch
end
module RoutingTestHelpers
- def url_for(set, options, recall = nil)
- set.send(:url_for, options.merge(:only_path => true, :_recall => recall))
+ def url_for(set, options, recall = {})
+ set.url_for options.merge(:only_path => true, :_recall => recall)
end
end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index 114bbf3c22..580604df3a 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -79,13 +79,13 @@ class AssertSelectTest < ActionController::TestCase
def test_assert_select
render_html %Q{<div id="1"></div><div id="2"></div>}
assert_select "div", 2
- assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" }
+ assert_failure(/\AExpected at least 1 element matching \"p\", found 0\.$/) { assert_select "p" }
end
def test_equality_integer
render_html %Q{<div id="1"></div><div id="2"></div>}
- assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) { assert_select "div", 3 }
- assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", 0 }
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) { assert_select "div", 3 }
+ assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", 0 }
end
def test_equality_true_false
@@ -100,13 +100,14 @@ class AssertSelectTest < ActionController::TestCase
def test_equality_false_message
render_html %Q{<div id="1"></div><div id="2"></div>}
- assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", false }
+ assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", false }
end
def test_equality_string_and_regexp
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", "foo" }
assert_raise(Assertion) { assert_select "div", "bar" }
+ assert_failure(/\A<bar> expected but was\n<foo>\.$/) { assert_select "div", "bar" }
assert_nothing_raised { assert_select "div", :text=>"foo" }
assert_raise(Assertion) { assert_select "div", :text=>"bar" }
assert_nothing_raised { assert_select "div", /(foo|bar)/ }
@@ -124,6 +125,7 @@ class AssertSelectTest < ActionController::TestCase
assert_raise(Assertion) { assert_select "p", html }
assert_nothing_raised { assert_select "p", :html=>html }
assert_raise(Assertion) { assert_select "p", :html=>text }
+ assert_failure(/\A<#{text}> expected but was\n<#{html}>\.$/) { assert_select "p", :html=>text }
# No stripping for pre.
render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>}
text = "\n\"This is not a big problem,\" he said.\n"
@@ -144,29 +146,29 @@ class AssertSelectTest < ActionController::TestCase
def test_counts
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", 2 }
- assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do
assert_select "div", 3
end
assert_nothing_raised { assert_select "div", 1..2 }
- assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do
assert_select "div", 3..4
end
assert_nothing_raised { assert_select "div", :count=>2 }
- assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do
assert_select "div", :count=>3
end
assert_nothing_raised { assert_select "div", :minimum=>1 }
assert_nothing_raised { assert_select "div", :minimum=>2 }
- assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected at least 3 elements matching \"div\", found 2\.$/) do
assert_select "div", :minimum=>3
end
assert_nothing_raised { assert_select "div", :maximum=>2 }
assert_nothing_raised { assert_select "div", :maximum=>3 }
- assert_failure(/Expected at most 1 element matching \"div\", found 2/) do
+ assert_failure(/\AExpected at most 1 element matching \"div\", found 2\.$/) do
assert_select "div", :maximum=>1
end
assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 }
- assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do
+ assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do
assert_select "div", :minimum=>3, :maximum=>4
end
end
@@ -204,7 +206,7 @@ class AssertSelectTest < ActionController::TestCase
end
end
- assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do
+ assert_failure(/\AExpected at least 1 element matching \"#4\", found 0\.$/) do
assert_select "div" do
assert_select "#4"
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 58a86ce9af..c0e6a2ebd1 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -227,6 +227,22 @@ CACHED
@store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}"))
end
+ def test_fragment_cache_instrumentation
+ payload = nil
+
+ subscriber = proc do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ payload = event.payload
+ end
+
+ ActiveSupport::Notifications.subscribed(subscriber, "read_fragment.action_controller") do
+ get :inline_fragment_cached
+ end
+
+ assert_equal "functional_caching", payload[:controller]
+ assert_equal "inline_fragment_cached", payload[:action]
+ end
+
def test_html_formatted_fragment_caching
get :formatted_fragment_cached, :format => "html"
assert_response :success
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 25a4857eba..3720a920d0 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -210,20 +210,29 @@ class FlashTest < ActionController::TestCase
end
def test_redirect_to_with_adding_flash_types
- @controller.class.add_flash_types :foo
+ original_controller = @controller
+ test_controller_with_flash_type_foo = Class.new(TestController) do
+ add_flash_types :foo
+ end
+ @controller = test_controller_with_flash_type_foo.new
get :redirect_with_foo_flash
assert_equal "for great justice", @controller.send(:flash)[:foo]
+ ensure
+ @controller = original_controller
end
- class SubclassesTestController < TestController; end
-
def test_add_flash_type_to_subclasses
- TestController.add_flash_types :foo
- assert SubclassesTestController._flash_types.include?(:foo)
+ test_controller_with_flash_type_foo = Class.new(TestController) do
+ add_flash_types :foo
+ end
+ subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo)
+ assert subclass_controller_with_no_flash_type._flash_types.include?(:foo)
end
- def test_do_not_add_flash_type_to_parent_class
- SubclassesTestController.add_flash_types :bar
+ def test_does_not_add_flash_type_to_parent_class
+ Class.new(TestController) do
+ add_flash_types :bar
+ end
assert_not TestController._flash_types.include?(:bar)
end
end
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 3655b90e32..00d4612ac9 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -93,8 +93,6 @@ class RedirectToSSL < ForceSSLController
end
class ForceSSLControllerLevelTest < ActionController::TestCase
- tests ForceSSLControllerLevel
-
def test_banana_redirects_to_https
get :banana
assert_response 301
@@ -115,8 +113,6 @@ class ForceSSLControllerLevelTest < ActionController::TestCase
end
class ForceSSLCustomOptionsTest < ActionController::TestCase
- tests ForceSSLCustomOptions
-
def setup
@request.env['HTTP_HOST'] = 'www.example.com:80'
end
@@ -189,8 +185,6 @@ class ForceSSLCustomOptionsTest < ActionController::TestCase
end
class ForceSSLOnlyActionTest < ActionController::TestCase
- tests ForceSSLOnlyAction
-
def test_banana_not_redirects_to_https
get :banana
assert_response 200
@@ -204,8 +198,6 @@ class ForceSSLOnlyActionTest < ActionController::TestCase
end
class ForceSSLExceptActionTest < ActionController::TestCase
- tests ForceSSLExceptAction
-
def test_banana_not_redirects_to_https
get :banana
assert_response 200
@@ -219,8 +211,6 @@ class ForceSSLExceptActionTest < ActionController::TestCase
end
class ForceSSLIfConditionTest < ActionController::TestCase
- tests ForceSSLIfCondition
-
def test_banana_not_redirects_to_https
get :banana
assert_response 200
@@ -234,8 +224,6 @@ class ForceSSLIfConditionTest < ActionController::TestCase
end
class ForceSSLFlashTest < ActionController::TestCase
- tests ForceSSLFlash
-
def test_cheeseburger_redirects_to_https
get :set_flash
assert_response 302
@@ -315,7 +303,6 @@ class ForceSSLOptionalSegmentsTest < ActionController::TestCase
end
class RedirectToSSLTest < ActionController::TestCase
- tests RedirectToSSL
def test_banana_redirects_to_https_if_not_https
get :banana
assert_response 301
@@ -334,4 +321,4 @@ class RedirectToSSLTest < ActionController::TestCase
assert_response 200
assert_equal 'ihaz', response.body
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 90548d4294..9052fc6962 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -129,6 +129,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_response :unauthorized
end
+ test "authentication request with wrong scheme" do
+ header = 'Bearer ' + encode_credentials('David', 'Goliath').split(' ', 2)[1]
+ @request.env['HTTP_AUTHORIZATION'] = header
+ get :search
+ assert_response :unauthorized
+ end
+
private
def encode_credentials(username, password)
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index e851cc6a63..214eab2f0d 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -374,6 +374,10 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
follow_redirect!
assert_response :success
assert_equal "/get", path
+
+ get '/moved'
+ assert_response :redirect
+ assert_redirected_to '/method'
end
end
@@ -511,6 +515,8 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
set.draw do
+ get 'moved' => redirect('/method')
+
match ':action', :to => controller, :via => [:get, :post], :as => :action
get 'get/:action', :to => controller, :as => :get_action
end
@@ -769,3 +775,34 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
assert_equal "/foo/1/edit", url_for(:action => 'edit', :only_path => true)
end
end
+
+class HeadWithStatusActionIntegrationTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def status
+ head :ok
+ end
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def app
+ self.class
+ end
+
+ routes.draw do
+ get "/foo/status" => 'head_with_status_action_integration_test/foo#status'
+ end
+
+ test "get /foo/status with head result does not cause stack overflow error" do
+ assert_nothing_raised do
+ get '/foo/status'
+ end
+ assert_response :ok
+ end
+end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 947f64176b..1dca36374a 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -39,6 +39,13 @@ module ActionController
ensure
sse.close
end
+
+ def sse_with_multiple_line_message
+ sse = SSE.new(response.stream)
+ sse.write("first line.\nsecond line.")
+ ensure
+ sse.close
+ end
end
tests SSETestController
@@ -87,6 +94,15 @@ module ActionController
assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
assert_match(/id: 2/, second_response)
end
+
+ def test_sse_with_multiple_line_message
+ get :sse_with_multiple_line_message
+
+ wait_for_response_stream_close
+ first_response, second_response = response.body.split("\n")
+ assert_match(/data: first line/, first_response)
+ assert_match(/data: second line/, second_response)
+ end
end
class LiveStreamTest < ActionController::TestCase
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
index c03c7edeb8..811c507af2 100644
--- a/actionpack/test/controller/mime/accept_format_test.rb
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -9,8 +9,6 @@ class StarStarMimeController < ActionController::Base
end
class StarStarMimeControllerTest < ActionController::TestCase
- tests StarStarMimeController
-
def test_javascript_with_format
@request.accept = "text/javascript"
get :index, :format => 'js'
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 499c62cc35..41503e11a8 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -258,8 +258,6 @@ class RespondToController < ActionController::Base
end
class RespondToControllerTest < ActionController::TestCase
- tests RespondToController
-
def setup
super
@request.host = "www.example.com"
@@ -492,6 +490,11 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal 'Whatever you ask for, I got it', @response.body
end
+ def test_handle_any_any_unkown_format
+ get :handle_any_any, { format: 'php' }
+ assert_equal 'Whatever you ask for, I got it', @response.body
+ end
+
def test_browser_check_with_any_any
@request.accept = "application/json, application/xml"
get :json_xml_or_html
diff --git a/actionpack/test/controller/mime/respond_with_test.rb b/actionpack/test/controller/mime/respond_with_test.rb
index a70592fa1b..115f3b2f41 100644
--- a/actionpack/test/controller/mime/respond_with_test.rb
+++ b/actionpack/test/controller/mime/respond_with_test.rb
@@ -2,6 +2,10 @@ require 'abstract_unit'
require 'controller/fake_models'
class RespondWithController < ActionController::Base
+ class CustomerWithJson < Customer
+ def to_json; super; end
+ end
+
respond_to :html, :json, :touch
respond_to :xml, :except => :using_resource_with_block
respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ]
@@ -38,6 +42,10 @@ class RespondWithController < ActionController::Base
respond_with(resource, :location => "http://test.host/", :status => :created)
end
+ def using_resource_with_json
+ respond_with(CustomerWithJson.new("david", request.delete? ? nil : 13))
+ end
+
def using_invalid_resource_with_template
respond_with(resource)
end
@@ -138,8 +146,6 @@ class EmptyRespondWithController < ActionController::Base
end
class RespondWithControllerTest < ActionController::TestCase
- tests RespondWithController
-
def setup
super
@request.host = "www.example.com"
@@ -382,9 +388,8 @@ class RespondWithControllerTest < ActionController::TestCase
end
def test_using_resource_for_put_with_json_yields_no_content_on_success
- Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')
@request.accept = "application/json"
- put :using_resource
+ put :using_resource_with_json
assert_equal "application/json", @response.content_type
assert_equal 204, @response.status
assert_equal "", @response.body
@@ -433,10 +438,9 @@ class RespondWithControllerTest < ActionController::TestCase
end
def test_using_resource_for_delete_with_json_yields_no_content_on_success
- Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')
Customer.any_instance.stubs(:destroyed?).returns(true)
@request.accept = "application/json"
- delete :using_resource
+ delete :using_resource_with_json
assert_equal "application/json", @response.content_type
assert_equal 204, @response.status
assert_equal "", @response.body
@@ -645,6 +649,8 @@ class RespondWithControllerTest < ActionController::TestCase
get :index, format: 'csv'
assert_equal Mime::CSV, @response.content_type
assert_equal "c,s,v", @response.body
+ ensure
+ ActionController::Renderers.remove :csv
end
def test_raises_missing_renderer_if_an_api_behavior_with_no_renderer
@@ -654,6 +660,23 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
+ def test_removing_renderers
+ ActionController::Renderers.add :csv do |obj, options|
+ send_data obj.to_csv, type: Mime::CSV
+ end
+ @controller = CsvRespondWithController.new
+ @request.accept = "text/csv"
+ get :index, format: 'csv'
+ assert_equal Mime::CSV, @response.content_type
+
+ ActionController::Renderers.remove :csv
+ assert_raise ActionController::MissingRenderer do
+ get :index, format: 'csv'
+ end
+ ensure
+ ActionController::Renderers.remove :csv
+ end
+
def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called
@controller = EmptyRespondWithController.new
@request.accept = "*/*"
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 1e2191d417..5b4885f7e0 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -6,7 +6,7 @@ module RenderImplicitAction
"render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
"render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!",
"render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented"
- )]
+ ), ActionView::FileSystemResolver.new(File.expand_path('../../../controller', __FILE__))]
def hello_world() end
end
@@ -33,10 +33,25 @@ module RenderImplicitAction
assert_status 200
end
+ test "render does not traverse the file system" do
+ assert_raises(AbstractController::ActionNotFound) do
+ action_name = %w(.. .. fixtures shared).join(File::SEPARATOR)
+ SimpleController.action(action_name).call(Rack::MockRequest.env_for("/"))
+ end
+ end
+
test "available_action? returns true for implicit actions" do
assert SimpleController.new.available_action?(:hello_world)
assert SimpleController.new.available_action?(:"hyphen-ated")
assert SimpleController.new.available_action?(:not_implemented)
end
+
+ test "available_action? does not allow File::SEPARATOR on the name" do
+ action_name = %w(evil .. .. path).join(File::SEPARATOR)
+ assert_equal false, SimpleController.new.available_action?(action_name.to_sym)
+
+ action_name = %w(evil path).join(File::SEPARATOR)
+ assert_equal false, SimpleController.new.available_action?(action_name.to_sym)
+ end
end
end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 99229b3baf..07c2115832 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -138,14 +138,14 @@ module RequestForgeryProtectionTests
assert_not_blocked do
get :index
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_render_button_to_with_token_tag
assert_not_blocked do
get :show_button
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_render_form_without_token_tag_if_remote
@@ -175,7 +175,7 @@ module RequestForgeryProtectionTests
assert_not_blocked do
get :form_for_remote_with_external_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
ensure
ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
end
@@ -185,21 +185,21 @@ module RequestForgeryProtectionTests
assert_not_blocked do
get :form_for_remote_with_external_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
end
def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested
assert_not_blocked do
get :form_for_remote_with_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_render_form_with_token_tag_with_authenticity_token_requested
assert_not_blocked do
get :form_for_with_token
end
- assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
end
def test_should_allow_get
@@ -462,16 +462,37 @@ end
class CustomAuthenticityParamControllerTest < ActionController::TestCase
def setup
super
- ActionController::Base.request_forgery_protection_token = :custom_token_name
+ @old_logger = ActionController::Base.logger
+ @logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ @token = "foobar"
+ ActionController::Base.request_forgery_protection_token = @token
end
def teardown
- ActionController::Base.request_forgery_protection_token = :authenticity_token
+ ActionController::Base.request_forgery_protection_token = nil
super
end
- def test_should_allow_custom_token
- post :index, :custom_token_name => 'foobar'
- assert_response :ok
+ def test_should_not_warn_if_form_authenticity_param_matches_form_authenticity_token
+ ActionController::Base.logger = @logger
+ SecureRandom.stubs(:base64).returns(@token)
+
+ begin
+ post :index, :custom_token_name => 'foobar'
+ assert_equal 0, @logger.logged(:warn).size
+ ensure
+ ActionController::Base.logger = @old_logger
+ end
+ end
+
+ def test_should_warn_if_form_authenticity_param_does_not_match_form_authenticity_token
+ ActionController::Base.logger = @logger
+
+ begin
+ post :index, :custom_token_name => 'bazqux'
+ assert_equal 1, @logger.logged(:warn).size
+ ensure
+ ActionController::Base.logger = @old_logger
+ end
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index df453a0251..b22bc2dc25 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -419,14 +419,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com'
end
routes = setup_for_named_route
- routes.expects(:url_for).with({
- :host => 'foo.com',
- :only_path => false,
- :controller => 'content',
- :action => 'show_page',
- :use_route => 'pages'
- }).once
- routes.send(:pages_url)
+ assert_equal "http://foo.com/page", routes.pages_url
end
def setup_for_named_route(options = {})
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 4df2f8b98d..aee139b95e 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -30,7 +30,6 @@ class SendFileWithActionControllerLive < SendFileController
end
class SendFileTest < ActionController::TestCase
- tests SendFileController
include TestFileUtils
Mime::Type.register "image/png", :png unless defined? Mime::PNG
@@ -201,8 +200,6 @@ class SendFileTest < ActionController::TestCase
end
end
- tests SendFileWithActionControllerLive
-
def test_send_file_with_action_controller_live
@controller = SendFileWithActionControllerLive.new
@controller.options = { :content_type => "application/x-ruby" }
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index a8035e5bd7..f52f8be101 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -11,6 +11,26 @@ module AbstractController
W.default_url_options.clear
end
+ def test_nested_optional
+ klass = Class.new {
+ include ActionDispatch::Routing::RouteSet.new.tap { |r|
+ r.draw {
+ get "/foo/(:bar/(:baz))/:zot", :as => 'fun',
+ :controller => :articles,
+ :action => :index
+ }
+ }.url_helpers
+ self.default_url_options[:host] = 'example.com'
+ }
+
+ path = klass.new.fun_path({:controller => :articles,
+ :baz => "baz",
+ :zot => "zot",
+ :only_path => true })
+ # :bar key isn't provided
+ assert_equal '/foo/zot', path
+ end
+
def add_host!
W.default_url_options[:host] = 'www.basecamphq.com'
end
@@ -169,6 +189,18 @@ module AbstractController
)
end
+ def test_without_protocol_and_with_port
+ add_host!
+ add_port!
+
+ assert_equal('//www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => '//')
+ )
+ assert_equal('//www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => false)
+ )
+ end
+
def test_trailing_slash
add_host!
options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index ba7aaa338d..0f145666d1 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -681,6 +681,123 @@ class CookiesTest < ActionController::TestCase
assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
end
+ def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 0dba651139..8660deb634 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -147,9 +147,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/", {}, xhr_request_env
assert_response 500
+ assert_no_match(/<header>/, body)
assert_no_match(/<body>/, body)
assert_equal response.content_type, "text/plain"
- assert_match(/puke/, body)
+ assert_match(/RuntimeError\npuke/, body)
get "/not_found", {}, xhr_request_env
assert_response 404
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
index 9e37b96951..e2b38c23bc 100644
--- a/actionpack/test/dispatch/header_test.rb
+++ b/actionpack/test/dispatch/header_test.rb
@@ -55,6 +55,8 @@ class HeaderTest < ActiveSupport::TestCase
test "key?" do
assert @headers.key?("CONTENT_TYPE")
assert @headers.include?("CONTENT_TYPE")
+ assert @headers.key?("Content-Type")
+ assert @headers.include?("Content-Type")
end
test "fetch with block" do
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index 08501d19c0..cd31e8e326 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -15,6 +15,9 @@ module TestGenerationPrefix
ActiveModel::Name.new(klass)
end
+
+ def to_model; self; end
+ def persisted?; true; end
end
class WithMountedEngine < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 6e21b4a258..b48e8ab974 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -152,9 +152,12 @@ class RequestIP < BaseRequestTest
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1'
assert_equal nil, request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::, fc01::, fdff'
assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'FE00::, FDFF::'
+ assert_equal 'FE00::', request.remote_ip
+
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
assert_equal nil, request.remote_ip
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
index 0e488d2b88..c465d56bde 100644
--- a/actionpack/test/dispatch/routing/route_set_test.rb
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -81,10 +81,6 @@ module ActionDispatch
end
private
- def clear!
- @set.clear!
- end
-
def draw(&block)
@set.draw(&block)
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index ab2f0ec8de..cae6b312b6 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1994,6 +1994,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'cards#destroy', @response.body
end
+ def test_shallow_deeply_nested_resources
+ draw do
+ resources :blogs do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ get '/comments/1'
+ assert_equal 'comments#show', @response.body
+
+ assert_equal '/comments/1', comment_path('1')
+ assert_equal '/blogs/new', new_blog_path
+ assert_equal '/blogs/1/posts/new', new_blog_post_path(:blog_id => 1)
+ assert_equal '/blogs/1/posts/2/comments/new', new_blog_post_comment_path(:blog_id => 1, :post_id => 2)
+ end
+
def test_shallow_nested_resources_within_scope
draw do
scope '/hello' do
@@ -3535,6 +3553,7 @@ class TestHttpMethods < ActionDispatch::IntegrationTest
RFC3648 = %w(ORDERPATCH)
RFC3744 = %w(ACL)
RFC5323 = %w(SEARCH)
+ RFC4791 = %w(MKCALENDAR)
RFC5789 = %w(PATCH)
def simple_app(response)
@@ -3546,13 +3565,13 @@ class TestHttpMethods < ActionDispatch::IntegrationTest
@app = ActionDispatch::Routing::RouteSet.new
@app.draw do
- (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789).each do |method|
+ (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method|
match '/' => s.simple_app(method), :via => method.underscore.to_sym
end
end
end
- (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789).each do |method|
+ (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method|
test "request method #{method.underscore} can be matched" do
get '/', nil, 'REQUEST_METHOD' => method
assert_equal method, @response.body
@@ -3578,8 +3597,8 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest
include Routes.url_helpers
def app; Routes end
- test 'escapes generated path segment' do
- assert_equal '/a%20b/c+d', segment_path(:segment => 'a b/c+d')
+ test 'escapes slash in generated path segment' do
+ assert_equal '/a%20b%2Fc+d', segment_path(:segment => 'a b/c+d')
end
test 'unescapes recognized path segment' do
@@ -3587,7 +3606,7 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest
assert_equal 'a b/c+d', @response.body
end
- test 'escapes generated path splat' do
+ test 'does not escape slash in generated path splat' do
assert_equal '/a%20b/c+d', splat_path(:splat => 'a b/c+d')
end
@@ -3772,6 +3791,8 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
get '/post(/:action(/:id))' => ok, as: :posts
get '/:foo/:foo_type/bars/:id' => ok, as: :bar
get '/projects/:id.:format' => ok, as: :project
+ get '/pages/:id' => ok, as: :page
+ get '/wiki/*page' => ok, as: :wiki
end
end
@@ -3804,6 +3825,26 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
assert_equal '/projects/1.json', Routes.url_helpers.project_path(1, :json)
assert_equal '/projects/1.json', project_path(1, :json)
end
+
+ test 'segments with question marks are escaped' do
+ assert_equal '/pages/foo%3Fbar', Routes.url_helpers.page_path('foo?bar')
+ assert_equal '/pages/foo%3Fbar', page_path('foo?bar')
+ end
+
+ test 'segments with slashes are escaped' do
+ assert_equal '/pages/foo%2Fbar', Routes.url_helpers.page_path('foo/bar')
+ assert_equal '/pages/foo%2Fbar', page_path('foo/bar')
+ end
+
+ test 'glob segments with question marks are escaped' do
+ assert_equal '/wiki/foo%3Fbar', Routes.url_helpers.wiki_path('foo?bar')
+ assert_equal '/wiki/foo%3Fbar', wiki_path('foo?bar')
+ end
+
+ test 'glob segments with slashes are not escaped' do
+ assert_equal '/wiki/foo/bar', Routes.url_helpers.wiki_path('foo/bar')
+ assert_equal '/wiki/foo/bar', wiki_path('foo/bar')
+ end
end
class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
@@ -4071,6 +4112,19 @@ class TestFormatConstraints < ActionDispatch::IntegrationTest
end
end
+class TestCallableConstraintValidation < ActionDispatch::IntegrationTest
+ def test_constraint_with_object_not_callable
+ assert_raises(ArgumentError) do
+ ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ get '/test', to: ok, constraints: Object.new
+ end
+ end
+ end
+ end
+end
+
class TestRouteDefaults < ActionDispatch::IntegrationTest
stub_controllers do |routes|
Routes = routes
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index 94969f795a..c3598c5e8e 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -196,6 +196,13 @@ class SSLTest < ActionDispatch::IntegrationTest
response.headers['Location']
end
+ def test_redirect_to_host_with_port
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org:443")
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org:443/path?key=value",
+ response.headers['Location']
+ end
+
def test_redirect_to_secure_host_when_on_subdomain
self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
get "http://ssl.example.org/path?key=value"
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 72f3d1db0d..9f6381f118 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -33,6 +33,12 @@ module ActionDispatch
assert_equal 'foo', uf.tempfile
end
+ def test_to_io_returns_the_tempfile
+ tf = Object.new
+ uf = Http::UploadedFile.new(:tempfile => tf)
+ assert_equal tf, uf.to_io
+ end
+
def test_delegates_path_to_tempfile
tf = Class.new { def path; 'thunderhorse' end }
uf = Http::UploadedFile.new(:tempfile => tf.new)
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index fdea27e2d2..910ff8a80f 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -64,18 +64,30 @@ module TestUrlGeneration
test "port is extracted from the host" do
assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "http://")
+ assert_equal "//www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "//")
+ assert_equal "//www.example.com:80/foo", foo_url(host: "www.example.com:80", protocol: "//")
+ end
+
+ test "port option is used" do
+ assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com", protocol: "http://", port: 8080)
+ assert_equal "//www.example.com:8080/foo", foo_url(host: "www.example.com", protocol: "//", port: 8080)
+ assert_equal "//www.example.com:80/foo", foo_url(host: "www.example.com", protocol: "//", port: 80)
end
test "port option overrides the host" do
assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: 8080)
+ assert_equal "//www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "//", port: 8080)
+ assert_equal "//www.example.com:80/foo", foo_url(host: "www.example.com:443", protocol: "//", port: 80)
end
test "port option disables the host when set to nil" do
assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: nil)
+ assert_equal "//www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "//", port: nil)
end
test "port option disables the host when set to false" do
assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: false)
+ assert_equal "//www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "//", port: false)
end
test "keep subdomain when key is true" do
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
index 93348f4647..584fd56a5c 100644
--- a/actionpack/test/journey/router/utils_test.rb
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -5,11 +5,15 @@ module ActionDispatch
class Router
class TestUtils < ActiveSupport::TestCase
def test_path_escape
- assert_equal "a/b%20c+d", Utils.escape_path("a/b c+d")
+ assert_equal "a/b%20c+d%25", Utils.escape_path("a/b c+d%")
+ end
+
+ def test_segment_escape
+ assert_equal "a%2Fb%20c+d%25", Utils.escape_segment("a/b c+d%")
end
def test_fragment_escape
- assert_equal "a/b%20c+d?e", Utils.escape_fragment("a/b c+d?e")
+ assert_equal "a/b%20c+d%25?e", Utils.escape_fragment("a/b c+d%?e")
end
def test_uri_unescape
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index a286f77633..dfd5ed8471 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -160,7 +160,7 @@ module ActionDispatch
]
assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(:path_info, nil, { :id => '10' }, { })
+ @formatter.generate(nil, { :id => '10' }, { })
end
end
@@ -169,11 +169,11 @@ module ActionDispatch
Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
]
- path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
+ path, _ = @formatter.generate(nil, { :id => '10' }, { })
assert_equal '/foo/10', path
assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
+ @formatter.generate(nil, { :id => 'aa' }, { })
end
end
@@ -182,13 +182,13 @@ module ActionDispatch
Router::Strexp.new("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
]
- path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
+ path, _ = @formatter.generate(nil, { :id => '10' }, { })
assert_equal '/foo/10', path
- path, _ = @formatter.generate(:path_info, nil, { }, { })
+ path, _ = @formatter.generate(nil, { }, { })
assert_equal '/foo', path
- path, _ = @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
+ path, _ = @formatter.generate(nil, { :id => 'aa' }, { })
assert_equal '/foo/aa', path
end
@@ -199,7 +199,7 @@ module ActionDispatch
@router.routes.add_route nil, path, {}, {}, route_name
error = assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(:path_info, route_name, { }, { })
+ @formatter.generate(route_name, { }, { })
end
assert_match(/missing required keys: \[:id\]/, error.message)
@@ -287,14 +287,14 @@ module ActionDispatch
def test_required_part_in_recall
add_routes @router, [ "/messages/:a/:b" ]
- path, _ = @formatter.generate(:path_info, nil, { :a => 'a' }, { :b => 'b' })
+ path, _ = @formatter.generate(nil, { :a => 'a' }, { :b => 'b' })
assert_equal "/messages/a/b", path
end
def test_splat_in_recall
add_routes @router, [ "/*path" ]
- path, _ = @formatter.generate(:path_info, nil, { }, { :path => 'b' })
+ path, _ = @formatter.generate(nil, { }, { :path => 'b' })
assert_equal "/b", path
end
@@ -304,7 +304,7 @@ module ActionDispatch
"/messages/:id(.:format)"
]
- path, _ = @formatter.generate(:path_info, nil, { :id => 10 }, { :action => 'index' })
+ path, _ = @formatter.generate(nil, { :id => 10 }, { :action => 'index' })
assert_equal "/messages/index/10", path
end
@@ -315,7 +315,7 @@ module ActionDispatch
params = { :controller => "tasks", :format => nil }
extras = { :action => 'lol' }
- path, _ = @formatter.generate(:path_info, nil, params, extras)
+ path, _ = @formatter.generate(nil, params, extras)
assert_equal '/tasks', path
end
@@ -327,7 +327,7 @@ module ActionDispatch
@router.routes.add_route @app, path, {}, {}, {}
- path, _ = @formatter.generate(:path_info, nil, Hash[params], {})
+ path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
end
@@ -340,7 +340,6 @@ module ActionDispatch
[:action, "show"] ]
@formatter.generate(
- :path_info,
nil,
Hash[params],
{},
@@ -354,7 +353,7 @@ module ActionDispatch
@router.routes.add_route @app, path, {}, {}, {}
path, params = @formatter.generate(
- :path_info, nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
+ nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
assert_equal '/tasks/show', path
assert_equal({:id => 1}, params)
end
@@ -363,18 +362,29 @@ module ActionDispatch
path = Path::Pattern.new '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
- path, _ = @formatter.generate(:path_info,
- nil, { :controller => "tasks",
+ path, _ = @formatter.generate(nil,
+ { :controller => "tasks",
:action => "a/b c+d",
}, {})
- assert_equal '/tasks/a/b%20c+d', path
+ assert_equal '/tasks/a%2Fb%20c+d', path
+ end
+
+ def test_generate_escapes_with_namespaced_controller
+ path = Path::Pattern.new '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ path, _ = @formatter.generate(
+ nil, { :controller => "admin/tasks",
+ :action => "a/b c+d",
+ }, {})
+ assert_equal '/admin/tasks/a%2Fb%20c+d', path
end
def test_generate_extra_params
path = Path::Pattern.new '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
- path, params = @formatter.generate(:path_info,
+ path, params = @formatter.generate(
nil, { :id => 1,
:controller => "tasks",
:action => "show",
@@ -388,7 +398,7 @@ module ActionDispatch
path = Path::Pattern.new '/:controller(/:action(/:id))'
@router.routes.add_route @app, path, {}, {}, {}
- path, params = @formatter.generate(:path_info,
+ path, params = @formatter.generate(
nil,
{:controller =>"tasks", :id => 10},
{:action =>"index"})
@@ -400,7 +410,7 @@ module ActionDispatch
path = Path::Pattern.new '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
- path, params = @formatter.generate(:path_info,
+ path, params = @formatter.generate(
"tasks",
{:controller=>"tasks"},
{:controller=>"tasks", :action=>"index"})
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 8c6db33be7..147e5b47db 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,75 @@
+* Allow custom `:host` option to be passed to `asset_url` helper that
+ overwrites `config.action_controller.asset_host` for particular asset.
+
+ *Hubert Łępicki*
+
+* Deprecate `AbstractController::Base.parent_prefixes`.
+ Override `AbstractController::Base.local_prefixes` when you want to change
+ where to find views.
+
+ *Nick Sutterer*
+
+* Take label values into account when doing I18n lookups for model attributes.
+
+ The following:
+
+ # form.html.erb
+ <%= form_for @post do |f| %>
+ <%= f.label :type, value: "long" %>
+ <% end %>
+
+ # en.yml
+ en:
+ activerecord:
+ attributes:
+ post/long: "Long-form Post"
+
+ Used to simply return "long", but now it will return "Long-form
+ Post".
+
+ *Joshua Cody*
+
+* Change `asset_path` to use File.join to create proper paths:
+
+ Before:
+
+ https://some.host.com//assets/some.js
+
+ After:
+
+ https://some.host.com/assets/some.js
+
+ *Peter Schröder*
+
+* Change `favicon_link_tag` default mimetype from `image/vnd.microsoft.icon` to
+ `image/x-icon`.
+
+ Before:
+
+ #=> favicon_link_tag 'myicon.ico'
+ <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+
+ After:
+
+ #=> favicon_link_tag 'myicon.ico'
+ <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
+
+ *Geoffroy Lorieux*
+
+* Remove wrapping div with inline styles for hidden form fields.
+
+ We are dropping HTML 4.01 and XHTML strict compliance since input tags directly
+ inside a form are valid HTML5, and the absense of inline styles help in validating
+ for Content Security Policy.
+
+ *Joost Baaij*
+
+* `collection_check_boxes` respects `:index` option for the hidden filed name.
+
+ Fixes #14147.
+
+ *Vasiliy Ermolovich*
+
* `date_select` helper with option `with_css_classes: true` does not overwrite other classes.
*Izumi Wong-Horiuchi*
@@ -28,5 +100,4 @@
*Piotr Chmolowski, Łukasz Strzałkowski*
-
Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index aa49f1edc1..824cdaa45e 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -142,22 +142,29 @@ module ActionView
)
end
- # Returns a link loading a favicon file. You may specify a different file
- # in the first argument. The helper accepts an additional options hash where
- # you can override "rel" and "type".
+ # Returns a link tag for a favicon managed by the asset pipeline.
#
- # ==== Options
+ # If a page has no link like the one generated by this helper, browsers
+ # ask for <tt>/favicon.ico</tt> automatically, and cache the file if the
+ # request succeeds. If the favicon changes it is hard to get it updated.
#
- # * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon'
- # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon'
+ # To have better control applications may let the asset pipeline manage
+ # their favicon storing the file under <tt>app/assets/images</tt>, and
+ # using this helper to generate its corresponding link tag.
#
- # ==== Examples
+ # The helper gets the name of the favicon file as first argument, which
+ # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options
+ # to override their defaults, "shortcut icon" and "image/x-icon"
+ # respectively:
+ #
+ # favicon_link_tag
+ # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
#
# favicon_link_tag 'myicon.ico'
- # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
#
- # Mobile Safari looks for a different <link> tag, pointing to an image that
- # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
+ # Mobile Safari looks for a different link tag, pointing to an image that
+ # will be used if you add the page to the home screen of an iOS device.
# The following call would generate such a tag:
#
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
@@ -165,7 +172,7 @@ module ActionView
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
- :type => 'image/vnd.microsoft.icon',
+ :type => 'image/x-icon',
:href => path_to_image(source)
}.merge!(options.symbolize_keys))
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index c830ab23e3..ae684af87b 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -134,11 +134,11 @@ module ActionView
relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
if relative_url_root
- source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/")
+ source = File.join(relative_url_root, source) unless source.starts_with?("#{relative_url_root}/")
end
if host = compute_asset_host(source, options)
- source = "#{host}#{source}"
+ source = File.join(host, source)
end
"#{source}#{tail}"
@@ -147,7 +147,14 @@ module ActionView
# Computes the full URL to an asset in the public directory. This
# will use +asset_path+ internally, so most of their behaviors
- # will be the same.
+ # will be the same. If :host options is set, it overwrites global
+ # +config.action_controller.asset_host+ setting.
+ #
+ # All other options provided are forwarded to +asset_path+ call.
+ #
+ # asset_url "application.js" # => http://example.com/application.js
+ # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/javascripts/application.js
+ #
def asset_url(source, options = {})
path_to_asset(source, options.merge(:protocol => :request))
end
@@ -191,7 +198,8 @@ module ActionView
# (proc or otherwise).
def compute_asset_host(source = "", options = {})
request = self.request if respond_to?(:request)
- host = config.asset_host if defined? config.asset_host
+ host = options[:host]
+ host ||= config.asset_host if defined? config.asset_host
host ||= request.base_url if request && options[:protocol] == :request
if host.respond_to?(:call)
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index af70a4242a..227ad4cdfa 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -10,7 +10,7 @@ module ActionView
# Full usage example:
#
# config/routes.rb:
- # Basecamp::Application.routes.draw do
+ # Rails.application.routes.draw do
# resources :posts
# root to: "posts#index"
# end
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index d1c268ec40..4db8930a26 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -11,7 +11,7 @@ module ActionView
# The best way to use this is by doing key-based cache expiration
# on top of a cache store like Memcached that'll automatically
# kick out old entries. For more on key-based expiration, see:
- # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
+ # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works
#
# When using this method, you list the cache dependency as the name of the cache, like so:
#
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 1ff090f244..180c4a62bf 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -449,7 +449,11 @@ module ActionView
method: method
)
- options[:url] ||= polymorphic_path(record, format: options.delete(:format))
+ options[:url] ||= if options.key?(:format)
+ polymorphic_path(record, format: options.delete(:format))
+ else
+ polymorphic_path(record, {})
+ end
end
private :apply_form_for_options!
@@ -746,6 +750,7 @@ module ActionView
# label(:post, :terms) do
# 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
+ # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(object_name, method, content_or_options = nil, options = nil, &block)
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 0bbe08166b..10dcb5c28c 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -83,13 +83,17 @@ module ActionView
# * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
# * <tt>:include_blank</tt> - If set to true, an empty option will be created.
- # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something
+ # * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something.
+ # * <tt>:selected</tt> - Provide a default selected value. It should be of the exact type as the provided options.
# * Any other key creates standard HTML attributes for the tag.
#
# ==== Examples
# select_tag "people", options_from_collection_for_select(@people, "id", "name")
# # <select id="people" name="people"><option value="1">David</option></select>
#
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name"), selected: ["1", "David"]
+ # # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
+ #
# select_tag "people", "<option>David</option>".html_safe
# # => <select id="people" name="people"><option>David</option></select>
#
@@ -550,6 +554,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # color_field_tag 'name'
+ # # => <input id="name" name="name" type="color" />
+ #
+ # color_field_tag 'color', '#DEF726'
+ # # => <input id="color" name="color" type="color" value="#DEF726" />
+ #
+ # color_field_tag 'color', nil, class: 'special_input'
+ # # => <input class="special_input" id="color" name="color" type="color" />
+ #
+ # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" />
def color_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
end
@@ -558,6 +575,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # search_field_tag 'name'
+ # # => <input id="name" name="name" type="search" />
+ #
+ # search_field_tag 'search', 'Enter your search query here'
+ # # => <input id="search" name="search" type="search" value="Enter your search query here" />
+ #
+ # search_field_tag 'search', nil, class: 'special_input'
+ # # => <input class="special_input" id="search" name="search" type="search" />
+ #
+ # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" />
def search_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "search"))
end
@@ -566,6 +596,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # telephone_field_tag 'name'
+ # # => <input id="name" name="name" type="tel" />
+ #
+ # telephone_field_tag 'tel', '0123456789'
+ # # => <input id="tel" name="tel" type="tel" value="0123456789" />
+ #
+ # telephone_field_tag 'tel', nil, class: 'special_input'
+ # # => <input class="special_input" id="tel" name="tel" type="tel" />
+ #
+ # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" />
def telephone_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "tel"))
end
@@ -575,6 +618,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # date_field_tag 'name'
+ # # => <input id="name" name="name" type="date" />
+ #
+ # date_field_tag 'date', '01/01/2014'
+ # # => <input id="date" name="date" type="date" value="01/01/2014" />
+ #
+ # date_field_tag 'date', nil, class: 'special_input'
+ # # => <input class="special_input" id="date" name="date" type="date" />
+ #
+ # date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" />
def date_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
end
@@ -638,6 +694,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # url_field_tag 'name'
+ # # => <input id="name" name="name" type="url" />
+ #
+ # url_field_tag 'url', 'http://rubyonrails.org'
+ # # => <input id="url" name="url" type="url" value="http://rubyonrails.org" />
+ #
+ # url_field_tag 'url', nil, class: 'special_input'
+ # # => <input class="special_input" id="url" name="url" type="url" />
+ #
+ # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" />
def url_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "url"))
end
@@ -646,6 +715,19 @@ module ActionView
#
# ==== Options
# * Accepts the same options as text_field_tag.
+ #
+ # ==== Examples
+ # email_field_tag 'name'
+ # # => <input id="name" name="name" type="email" />
+ #
+ # email_field_tag 'email', 'email@example.com'
+ # # => <input id="email" name="email" type="email" value="email@example.com" />
+ #
+ # email_field_tag 'email', nil, class: 'special_input'
+ # # => <input class="special_input" id="email" name="email" type="email" />
+ #
+ # email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" />
def email_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "email"))
end
@@ -657,12 +739,40 @@ module ActionView
# * <tt>:max</tt> - The maximum acceptable value.
# * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
# <tt>:max</tt> values.
+ # * <tt>:within</tt> - Same as <tt>:in</tt>.
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
#
# ==== Examples
+ # number_field_tag 'quantity'
+ # # => <input id="quantity" name="quantity" type="number" />
+ #
+ # number_field_tag 'quantity', '1'
+ # # => <input id="quantity" name="quantity" type="number" value="1" />
+ #
+ # number_field_tag 'quantity', nil, class: 'special_input'
+ # # => <input class="special_input" id="quantity" name="quantity" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1
+ # # => <input id="quantity" name="quantity" min="1" type="number" />
+ #
+ # number_field_tag 'quantity', nil, max: 9
+ # # => <input id="quantity" name="quantity" max="9" type="number" />
+ #
# number_field_tag 'quantity', nil, in: 1...10
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, within: 1...10
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1, max: 10
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
+ #
+ # number_field_tag 'quantity', nil, min: 1, max: 10, step: 2
+ # # => <input id="quantity" name="quantity" min="1" max="9" step="2" type="number" />
+ #
+ # number_field_tag 'quantity', '1', class: 'special_input', disabled: true
+ # # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" />
def number_field_tag(name, value = nil, options = {})
options = options.stringify_keys
options["type"] ||= "number"
@@ -726,9 +836,11 @@ module ActionView
method_tag(method) + token_tag(authenticity_token)
end
- enforce_utf8 = html_options.delete("enforce_utf8") { true }
- tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag
- content_tag(:div, tags, :style => 'display:none')
+ if html_options.delete("enforce_utf8") { true }
+ utf8_enforcer_tag + method_tag
+ else
+ method_tag
+ end
end
def form_tag_html(html_options)
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 8b28e4fc33..6242a2a085 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -28,10 +28,7 @@ module ActionView
# Append a hidden field to make sure something will be sent back to the
# server if all check boxes are unchecked.
if @options.fetch(:include_hidden, true)
- hidden_name = @html_options[:name] || "#{tag_name}[]"
- hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil)
-
- rendered_collection + hidden
+ rendered_collection + hidden_field
else
rendered_collection
end
@@ -42,6 +39,18 @@ module ActionView
def render_component(builder)
builder.check_box + builder.label
end
+
+ def hidden_field
+ hidden_name = @html_options[:name]
+
+ hidden_name ||= if @options.has_key?(:index)
+ "#{tag_name_with_index(@options[:index])}[]"
+ else
+ "#{tag_name}[]"
+ end
+
+ @template_object.hidden_field_tag(hidden_name, "", id: nil)
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index 991f32cea2..8050638363 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -44,7 +44,7 @@ module ActionView
def default_html_options_for_collection(item, value) #:nodoc:
html_options = @html_options.dup
- [:checked, :selected, :disabled].each do |option|
+ [:checked, :selected, :disabled, :readonly].each do |option|
current_value = @options[option]
next if current_value.nil?
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 6335e3dd4d..a5bcaf8153 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -35,9 +35,9 @@ module ActionView
if block_given?
content = @template_object.capture(&block)
else
+ method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
content = if @content.blank?
@object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1')
- method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
if object.respond_to?(:to_model)
key = object.class.model_name.i18n_key
@@ -51,7 +51,7 @@ module ActionView
end
content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(@method_name)
+ object.class.human_attribute_name(method_and_value)
end
content ||= @method_name.humanize
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 0bc40874d9..17ec6a40bf 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -7,7 +7,7 @@ module ActionView
module TranslationHelper
# Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
#
- # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
+ # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
# into inline spans that:
#
# * have a "translation-missing" class set,
@@ -34,6 +34,7 @@ module ActionView
# naming convention helps to identify translations that include HTML tags so that
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
+ options = options.dup
options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
# If the user has specified rescue_format then pass it all through, otherwise use
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 89c196e578..894616a449 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -323,7 +323,7 @@ module ActionView
inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
end
end
- content_tag('form', content_tag('div', inner_tags), form_options)
+ content_tag('form', inner_tags, form_options)
end
# Creates a link tag of the given +name+ using a URL created by the set of
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 855fed0190..5fff6b0771 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -114,7 +114,7 @@ module ActionView
module ViewPaths
attr_reader :view_paths, :html_fallback_for_js
- # Whenever setting view paths, makes a copy so we can manipulate then in
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
# instance objects as we wish.
def view_paths=(paths)
@view_paths = ActionView::PathSet.new(Array(paths))
@@ -134,7 +134,8 @@ module ActionView
end
alias :template_exists? :exists?
- # Add fallbacks to the view paths. Useful in cases you are rendering a :file.
+ # Adds fallbacks to the view paths. Useful in cases when you are rendering
+ # a :file.
def with_fallbacks
added_resolvers = 0
self.class.fallbacks.each do |resolver|
@@ -227,7 +228,7 @@ module ActionView
end
# Overload locale= to also set the I18n.locale. If the current I18n.config object responds
- # to original_config, it means that it's has a copy of the original I18n configuration and it's
+ # to original_config, it means that it has a copy of the original I18n configuration and it's
# acting as proxy, which we need to skip.
def locale=(value)
if value
@@ -238,7 +239,7 @@ module ActionView
super(@skip_default_locale ? I18n.locale : default_locale)
end
- # A method which only uses the first format in the formats array for layout lookup.
+ # Uses the first format in the formats array for layout lookup.
def with_layout_format
if formats.size == 1
yield
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 33be06cbf7..881a123572 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -1,3 +1,5 @@
+require 'action_dispatch/routing/polymorphic_routes'
+
module ActionView
module RoutingUrlFor
@@ -77,16 +79,20 @@ module ActionView
case options
when String
options
- when nil, Hash
- options ||= {}
- options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys)
- super
+ when nil
+ super({:only_path => true})
+ when Hash
+ super({ :only_path => options[:host].nil? }.merge!(options.symbolize_keys))
when :back
_back_url
+ when Symbol
+ ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_string_call self, options
when Array
polymorphic_path(options, options.extract_options!)
+ when Class
+ ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_class_call self, options
else
- polymorphic_path(options)
+ ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call self, options
end
end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 403824bd8e..d29d020c17 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -181,13 +181,7 @@ module ActionView
def query(path, details, formats)
query = build_query(path, details)
- # deals with case-insensitive file systems.
- sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
-
- template_paths = Dir[query].reject { |filename|
- File.directory?(filename) ||
- !sanitizer[File.dirname(filename)].include?(filename)
- }
+ template_paths = find_template_paths query
template_paths.map { |template|
handler, format, variant = extract_handler_and_format_and_variant(template, formats)
@@ -202,6 +196,26 @@ module ActionView
}
end
+ if RUBY_VERSION >= '2.2.0'
+ def find_template_paths(query)
+ Dir[query].reject { |filename|
+ File.directory?(filename) ||
+ # deals with case-insensitive file systems.
+ !File.fnmatch(query, filename, File::FNM_EXTGLOB)
+ }
+ end
+ else
+ def find_template_paths(query)
+ # deals with case-insensitive file systems.
+ sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
+
+ Dir[query].reject { |filename|
+ File.directory?(filename) ||
+ !sanitizer[File.dirname(filename)].include?(filename)
+ }
+ end
+ end
+
# Helper for building query glob string based on resolver's pattern.
def build_query(path, details)
query = @pattern.dup
@@ -228,7 +242,7 @@ module ActionView
File.mtime(p)
end
- # Extract handler and formats from path. If a format cannot be a found neither
+ # Extract handler, formats and variant from path. If a format cannot be found neither
# from the path, or the handler, we should return the array of formats given
# to the resolver.
def extract_handler_and_format_and_variant(path, default_formats)
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 3145446114..9e8e6f43d5 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -235,7 +235,8 @@ module ActionView
:@options,
:@test_passed,
:@view,
- :@view_context_class
+ :@view_context_class,
+ :@_subscribers
]
def _user_defined_ivars
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 6c349feb1d..80a41f2418 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -14,27 +14,38 @@ module ActionView
:locale, :locale=, :to => :lookup_context
module ClassMethods
- def parent_prefixes
- @parent_prefixes ||= begin
- parent_controller = superclass
- prefixes = []
-
- until parent_controller.abstract?
- prefixes << parent_controller.controller_path
- parent_controller = parent_controller.superclass
- end
+ def _prefixes # :nodoc:
+ @_prefixes ||= begin
+ deprecated_prefixes = handle_deprecated_parent_prefixes
+ if deprecated_prefixes
+ deprecated_prefixes
+ else
+ return local_prefixes if superclass.abstract?
- prefixes
+ local_prefixes + superclass._prefixes
+ end
end
end
+
+ private
+
+ # Override this method in your controller if you want to change paths prefixes for finding views.
+ # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
+ def local_prefixes
+ [controller_path]
+ end
+
+ def handle_deprecated_parent_prefixes # TODO: remove in 4.3/5.0.
+ return unless respond_to?(:parent_prefixes)
+
+ ActiveSupport::Deprecation.warn "Overriding ActionController::Base::parent_prefixes is deprecated, override .local_prefixes instead."
+ local_prefixes + parent_prefixes
+ end
end
# The prefixes used in render "foo" shortcuts.
- def _prefixes
- @_prefixes ||= begin
- parent_prefixes = self.class.parent_prefixes
- parent_prefixes.dup.unshift(controller_path)
- end
+ def _prefixes # :nodoc:
+ self.class._prefixes
end
# LookupContext is the object responsible to hold all information required to lookup
diff --git a/actionview/test/actionpack/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb
index 40d3b17131..e653b12d32 100644
--- a/actionview/test/actionpack/abstract/abstract_controller_test.rb
+++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb
@@ -150,6 +150,54 @@ module AbstractController
end
end
+ class OverridingLocalPrefixes < AbstractController::Base
+ include AbstractController::Rendering
+ include ActionView::Rendering
+ append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views"))
+
+ def index
+ render
+ end
+
+ def self.local_prefixes
+ # this would usually return "abstract_controller/testing/overriding_local_prefixes"
+ super + ["abstract_controller/testing/me3"]
+ end
+
+ class Inheriting < self
+ end
+ end
+
+ class OverridingLocalPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3.
+ test "overriding .local_prefixes adds prefix" do
+ @controller = OverridingLocalPrefixes.new
+ @controller.process(:index)
+ assert_equal "Hello from me3/index.erb", @controller.response_body
+ end
+
+ test ".local_prefixes is inherited" do
+ @controller = OverridingLocalPrefixes::Inheriting.new
+ @controller.process(:index)
+ assert_equal "Hello from me3/index.erb", @controller.response_body
+ end
+ end
+
+ class DeprecatedParentPrefixes < OverridingLocalPrefixes
+ def self.parent_prefixes
+ ["abstract_controller/testing/me3"]
+ end
+ end
+
+ class DeprecatedParentPrefixesTest < ActiveSupport::TestCase # TODO: remove me in 5.0/4.3.
+ test "overriding .parent_prefixes is deprecated" do
+ @controller = DeprecatedParentPrefixes.new
+ assert_deprecated do
+ @controller.process(:index)
+ end
+ assert_equal "Hello from me3/index.erb", @controller.response_body
+ end
+ end
+
# Test rendering with layouts
# ====
# self._layout is used when defined
diff --git a/actionview/test/actionpack/abstract/render_test.rb b/actionview/test/actionpack/abstract/render_test.rb
index f9d8c916d9..d09f91c1e2 100644
--- a/actionview/test/actionpack/abstract/render_test.rb
+++ b/actionview/test/actionpack/abstract/render_test.rb
@@ -60,42 +60,42 @@ module AbstractController
end
def test_render_template
- @controller.process(:template)
+ assert_equal "With Template", @controller.process(:template)
assert_equal "With Template", @controller.response_body
end
def test_render_file
- @controller.process(:file)
+ assert_equal "With File", @controller.process(:file)
assert_equal "With File", @controller.response_body
end
def test_render_inline
- @controller.process(:inline)
+ assert_equal "With Inline", @controller.process(:inline)
assert_equal "With Inline", @controller.response_body
end
def test_render_text
- @controller.process(:text)
+ assert_equal "With Text", @controller.process(:text)
assert_equal "With Text", @controller.response_body
end
def test_render_default
- @controller.process(:default)
+ assert_equal "With Default", @controller.process(:default)
assert_equal "With Default", @controller.response_body
end
def test_render_string
- @controller.process(:string)
+ assert_equal "With String", @controller.process(:string)
assert_equal "With String", @controller.response_body
end
def test_render_symbol
- @controller.process(:symbol)
+ assert_equal "With Symbol", @controller.process(:symbol)
assert_equal "With Symbol", @controller.response_body
end
def test_render_string_with_path
- @controller.process(:string_with_path)
+ assert_equal "With String With Path", @controller.process(:string_with_path)
assert_equal "With String With Path", @controller.response_body
end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 45b8049b83..ab7b961ed2 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -720,6 +720,11 @@ class RenderTest < ActionController::TestCase
assert_equal "Elastica", @response.body
end
+ def test_render_process
+ get :render_action_hello_world_as_string
+ assert_equal ["Hello world!"], @controller.process(:render_action_hello_world_as_string)
+ end
+
# :ported:
def test_render_from_variable
get :render_hello_world_from_variable
@@ -1332,4 +1337,3 @@ class RenderTest < ActionController::TestCase
assert_equal "Before (Anthony)\nInside from partial (Anthony)\nAfter\nBefore (David)\nInside from partial (David)\nAfter\nBefore (Ramm)\nInside from partial (Ramm)\nAfter", @response.body
end
end
-
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 0a9628da8d..0a62f49f35 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -59,12 +59,13 @@ class FormHelperActiveRecordTest < ActionView::TestCase
protected
def hidden_fields(method = nil)
- txt = %{<div style="display:none">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+
if method && !%w(get post).include?(method.to_s)
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
- txt << %{</div>}
+
+ txt
end
def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
@@ -88,4 +89,4 @@ class FormHelperActiveRecordTest < ActionView::TestCase
form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
end
-end \ No newline at end of file
+end
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index afb714484b..fef27ef492 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -74,25 +74,77 @@ class PolymorphicRoutesTest < ActionController::TestCase
@blog_blog = Blog::Blog.new
end
+ def assert_url(url, args)
+ host = self.class.default_url_options[:host]
+
+ assert_equal url.sub(/http:\/\/#{host}/, ''), polymorphic_path(args)
+ assert_equal url, polymorphic_url(args)
+ assert_equal url, url_for(args)
+ end
+
+ def test_string
+ with_test_routes do
+ # FIXME: why are these different? Symbol case passes through to
+ # `polymorphic_url`, but the String case doesn't.
+ assert_equal "http://example.com/projects", polymorphic_url("projects")
+ assert_equal "projects", url_for("projects")
+ end
+ end
+
+ def test_string_with_options
+ with_test_routes do
+ assert_equal "http://example.com/projects?id=10", polymorphic_url("projects", :id => 10)
+ end
+ end
+
+ def test_symbol
+ with_test_routes do
+ assert_url "http://example.com/projects", :projects
+ end
+ end
+
+ def test_symbol_with_options
+ with_test_routes do
+ assert_equal "http://example.com/projects?id=10", polymorphic_url(:projects, :id => 10)
+ end
+ end
+
def test_passing_routes_proxy
with_namespaced_routes(:blog) do
proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self)
@blog_post.save
- assert_equal "http://example.com/posts/#{@blog_post.id}", polymorphic_url([proxy, @blog_post])
+ assert_url "http://example.com/posts/#{@blog_post.id}", [proxy, @blog_post]
end
end
def test_namespaced_model
with_namespaced_routes(:blog) do
@blog_post.save
- assert_equal "http://example.com/posts/#{@blog_post.id}", polymorphic_url(@blog_post)
+ assert_url "http://example.com/posts/#{@blog_post.id}", @blog_post
end
end
def test_namespaced_model_with_name_the_same_as_namespace
with_namespaced_routes(:blog) do
@blog_blog.save
- assert_equal "http://example.com/blogs/#{@blog_blog.id}", polymorphic_url(@blog_blog)
+ assert_url "http://example.com/blogs/#{@blog_blog.id}", @blog_blog
+ end
+ end
+
+ def test_polymorphic_url_with_2_objects
+ with_namespaced_routes(:blog) do
+ @blog_blog.save
+ @blog_post.save
+ assert_equal "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}", polymorphic_url([@blog_blog, @blog_post])
+ end
+ end
+
+ def test_polymorphic_url_with_3_objects
+ with_namespaced_routes(:blog) do
+ @blog_blog.save
+ @blog_post.save
+ @fax.save
+ assert_equal "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}/faxes/#{@fax.id}", polymorphic_url([@blog_blog, @blog_post, @fax])
end
end
@@ -100,7 +152,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_namespaced_routes(:blog) do
@blog_post.save
@blog_blog.save
- assert_equal "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}", polymorphic_url([@blog_blog, @blog_post])
+ assert_url "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}", [@blog_blog, @blog_post]
end
end
@@ -112,29 +164,88 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_with_empty_list
+ with_test_routes do
+ assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ polymorphic_url([])
+ end
+ end
+ end
+
+ def test_with_nil_id
+ with_test_routes do
+ assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ polymorphic_url({ :id => nil })
+ end
+ end
+ end
+
+ def test_with_nil_in_list
+ with_test_routes do
+ assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ @series.save
+ polymorphic_url([nil, @series])
+ end
+ end
+ end
+
def test_with_record
with_test_routes do
@project.save
- assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url(@project)
+ assert_url "http://example.com/projects/#{@project.id}", @project
end
end
def test_with_class
with_test_routes do
- assert_equal "http://example.com/projects", polymorphic_url(@project.class)
+ assert_url "http://example.com/projects", @project.class
+ end
+ end
+
+ def test_with_class_list_of_one
+ with_test_routes do
+ assert_url "http://example.com/projects", [@project.class]
+ end
+ end
+
+ def test_class_with_options
+ with_test_routes do
+ assert_equal "http://example.com/projects?foo=bar", polymorphic_url(@project.class, { :foo => :bar })
+ assert_equal "/projects?foo=bar", polymorphic_path(@project.class, { :foo => :bar })
end
end
def test_with_new_record
with_test_routes do
- assert_equal "http://example.com/projects", polymorphic_url(@project)
+ assert_url "http://example.com/projects", @project
+ end
+ end
+
+ def test_new_record_arguments
+ params = nil
+
+ with_test_routes do
+ extend Module.new {
+ define_method("projects_url") { |*args|
+ params = args
+ super(*args)
+ }
+
+ define_method("projects_path") { |*args|
+ params = args
+ super(*args)
+ }
+ }
+
+ assert_url "http://example.com/projects", @project
+ assert_equal [], params
end
end
def test_with_destroyed_record
with_test_routes do
@project.destroy
- assert_equal "http://example.com/projects", polymorphic_url(@project)
+ assert_url "http://example.com/projects", @project
end
end
@@ -196,14 +307,14 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_test_routes do
@project.save
@task.save
- assert_equal "http://example.com/projects/#{@project.id}/tasks/#{@task.id}", polymorphic_url([@project, @task])
+ assert_url "http://example.com/projects/#{@project.id}/tasks/#{@task.id}", [@project, @task]
end
end
def test_with_nested_unsaved
with_test_routes do
@project.save
- assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task])
+ assert_url "http://example.com/projects/#{@project.id}/tasks", [@project, @task]
end
end
@@ -211,20 +322,20 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_test_routes do
@project.save
@task.destroy
- assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task])
+ assert_url "http://example.com/projects/#{@project.id}/tasks", [@project, @task]
end
end
def test_with_nested_class
with_test_routes do
@project.save
- assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task.class])
+ assert_url "http://example.com/projects/#{@project.id}/tasks", [@project, @task.class]
end
end
def test_class_with_array_and_namespace
with_admin_test_routes do
- assert_equal "http://example.com/admin/projects", polymorphic_url([:admin, @project.class])
+ assert_url "http://example.com/admin/projects", [:admin, @project.class]
end
end
@@ -236,14 +347,14 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_unsaved_with_array_and_namespace
with_admin_test_routes do
- assert_equal "http://example.com/admin/projects", polymorphic_url([:admin, @project])
+ assert_url "http://example.com/admin/projects", [:admin, @project]
end
end
def test_nested_unsaved_with_array_and_namespace
with_admin_test_routes do
@project.save
- assert_equal "http://example.com/admin/projects/#{@project.id}/tasks", polymorphic_url([:admin, @project, @task])
+ assert_url "http://example.com/admin/projects/#{@project.id}/tasks", [:admin, @project, @task]
end
end
@@ -251,7 +362,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_admin_test_routes do
@project.save
@task.save
- assert_equal "http://example.com/admin/projects/#{@project.id}/tasks/#{@task.id}", polymorphic_url([:admin, @project, @task])
+ assert_url "http://example.com/admin/projects/#{@project.id}/tasks/#{@task.id}", [:admin, @project, @task]
end
end
@@ -260,14 +371,14 @@ class PolymorphicRoutesTest < ActionController::TestCase
@project.save
@task.save
@step.save
- assert_equal "http://example.com/admin/projects/#{@project.id}/site/tasks/#{@task.id}/steps/#{@step.id}", polymorphic_url([:admin, @project, :site, @task, @step])
+ assert_url "http://example.com/admin/projects/#{@project.id}/site/tasks/#{@task.id}/steps/#{@step.id}", [:admin, @project, :site, @task, @step]
end
end
def test_nesting_with_array_ending_in_singleton_resource
with_test_routes do
@project.save
- assert_equal "http://example.com/projects/#{@project.id}/bid", polymorphic_url([@project, :bid])
+ assert_url "http://example.com/projects/#{@project.id}/bid", [@project, :bid]
end
end
@@ -275,7 +386,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_test_routes do
@project.save
@task.save
- assert_equal "http://example.com/projects/#{@project.id}/bid/tasks/#{@task.id}", polymorphic_url([@project, :bid, @task])
+ assert_url "http://example.com/projects/#{@project.id}/bid/tasks/#{@task.id}", [@project, :bid, @task]
end
end
@@ -291,34 +402,40 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_admin_test_routes do
@project.save
@task.save
- assert_equal "http://example.com/admin/projects/#{@project.id}/bid/tasks/#{@task.id}", polymorphic_url([:admin, @project, :bid, @task])
+ assert_url "http://example.com/admin/projects/#{@project.id}/bid/tasks/#{@task.id}", [:admin, @project, :bid, @task]
end
end
- def test_nesting_with_array_containing_nil
+ def test_nesting_with_array
with_test_routes do
@project.save
- assert_equal "http://example.com/projects/#{@project.id}/bid", polymorphic_url([@project, nil, :bid])
+ assert_url "http://example.com/projects/#{@project.id}/bid", [@project, :bid]
end
end
def test_with_array_containing_single_object
with_test_routes do
@project.save
- assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url([nil, @project])
+ assert_url "http://example.com/projects/#{@project.id}", [@project]
end
end
def test_with_array_containing_single_name
with_test_routes do
@project.save
- assert_equal "http://example.com/projects", polymorphic_url([:projects])
+ assert_url "http://example.com/projects", [:projects]
+ end
+ end
+
+ def test_with_array_containing_single_string_name
+ with_test_routes do
+ assert_url "http://example.com/projects", ["projects"]
end
end
def test_with_array_containing_symbols
with_test_routes do
- assert_equal "http://example.com/series/new", polymorphic_url([:new, :series])
+ assert_url "http://example.com/series/new", [:new, :series]
end
end
@@ -353,26 +470,26 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_with_irregular_plural_record
with_test_routes do
@tax.save
- assert_equal "http://example.com/taxes/#{@tax.id}", polymorphic_url(@tax)
+ assert_url "http://example.com/taxes/#{@tax.id}", @tax
end
end
def test_with_irregular_plural_class
with_test_routes do
- assert_equal "http://example.com/taxes", polymorphic_url(@tax.class)
+ assert_url "http://example.com/taxes", @tax.class
end
end
def test_with_irregular_plural_new_record
with_test_routes do
- assert_equal "http://example.com/taxes", polymorphic_url(@tax)
+ assert_url "http://example.com/taxes", @tax
end
end
def test_with_irregular_plural_destroyed_record
with_test_routes do
@tax.destroy
- assert_equal "http://example.com/taxes", polymorphic_url(@tax)
+ assert_url "http://example.com/taxes", @tax
end
end
@@ -406,7 +523,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_with_nested_unsaved_irregular_plurals
with_test_routes do
@tax.save
- assert_equal "http://example.com/taxes/#{@tax.id}/faxes", polymorphic_url([@tax, @fax])
+ assert_url "http://example.com/taxes/#{@tax.id}/faxes", [@tax, @fax]
end
end
@@ -418,34 +535,34 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_class_with_irregular_plural_array_and_namespace
with_admin_test_routes do
- assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax.class])
+ assert_url "http://example.com/admin/taxes", [:admin, @tax.class]
end
end
def test_unsaved_with_irregular_plural_array_and_namespace
with_admin_test_routes do
- assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax])
+ assert_url "http://example.com/admin/taxes", [:admin, @tax]
end
end
def test_nesting_with_irregular_plurals_and_array_ending_in_singleton_resource
with_test_routes do
@tax.save
- assert_equal "http://example.com/taxes/#{@tax.id}/bid", polymorphic_url([@tax, :bid])
+ assert_url "http://example.com/taxes/#{@tax.id}/bid", [@tax, :bid]
end
end
def test_with_array_containing_single_irregular_plural_object
with_test_routes do
@tax.save
- assert_equal "http://example.com/taxes/#{@tax.id}", polymorphic_url([nil, @tax])
+ assert_url "http://example.com/taxes/#{@tax.id}", [@tax]
end
end
def test_with_array_containing_single_name_irregular_plural
with_test_routes do
@tax.save
- assert_equal "http://example.com/taxes", polymorphic_url([:taxes])
+ assert_url "http://example.com/taxes", [:taxes]
end
end
@@ -453,15 +570,15 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_uncountable_resource
with_test_routes do
@series.save
- assert_equal "http://example.com/series/#{@series.id}", polymorphic_url(@series)
- assert_equal "http://example.com/series", polymorphic_url(Series.new)
+ assert_url "http://example.com/series/#{@series.id}", @series
+ assert_url "http://example.com/series", Series.new
end
end
def test_routing_a_to_model_delegate
with_test_routes do
@delegator.save
- assert_equal "http://example.com/model_delegates/overridden", polymorphic_url(@delegator)
+ assert_url "http://example.com/model_delegates/overridden", @delegator
end
end
@@ -470,13 +587,15 @@ class PolymorphicRoutesTest < ActionController::TestCase
set.draw do
scope(:module => name) do
resources :blogs do
- resources :posts
+ resources :posts do
+ resources :faxes
+ end
end
resources :posts
end
end
- self.class.send(:include, @routes.url_helpers)
+ extend @routes.url_helpers
yield
end
end
@@ -498,7 +617,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
resources :model_delegates
end
- self.class.send(:include, @routes.url_helpers)
+ extend @routes.url_helpers
yield
end
end
@@ -520,7 +639,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- self.class.send(:include, @routes.url_helpers)
+ extend @routes.url_helpers
yield
end
end
@@ -539,8 +658,21 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
- self.class.send(:include, @routes.url_helpers)
+ extend @routes.url_helpers
yield
end
end
end
+
+class PolymorphicPathRoutesTest < PolymorphicRoutesTest
+ include ActionView::RoutingUrlFor
+ include ActionView::Context
+
+ attr_accessor :controller
+
+ def assert_url(url, args)
+ host = self.class.default_url_options[:host]
+
+ assert_equal url.sub(/http:\/\/#{host}/, ''), url_for(args)
+ end
+end
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 3ca71d3376..343681b5a9 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -195,9 +195,9 @@ class AssetTagHelperTest < ActionView::TestCase
}
FaviconLinkToTag = {
- %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
- %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
- %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />),
+ %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />),
+ %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />),
+ %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/x-icon" />),
%(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />),
%(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(<link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />)
}
@@ -309,6 +309,14 @@ class AssetTagHelperTest < ActionView::TestCase
AssetPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_asset_path_tag_to_not_create_duplicate_slashes
+ @controller.config.asset_host = "host/"
+ assert_dom_equal('http://host/foo', asset_path("foo"))
+
+ @controller.config.relative_url_root = '/some/root/'
+ assert_dom_equal('http://host/some/root/foo', asset_path("foo"))
+ end
+
def test_compute_asset_public_path
assert_equal "/robots.txt", compute_asset_path("robots.txt")
assert_equal "/robots.txt", compute_asset_path("/robots.txt")
@@ -588,6 +596,10 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_equal "gopher://www.example.com", compute_asset_host("foo", :protocol => :request)
end
+ def test_should_return_custom_host_if_passed_in_options
+ assert_equal "http://custom.example.com", compute_asset_host("foo", :host => "http://custom.example.com")
+ end
+
def test_should_ignore_relative_root_path_on_complete_url
assert_dom_equal(%(http://www.example.com/images/xml.png), image_path("http://www.example.com/images/xml.png"))
end
@@ -751,4 +763,15 @@ class AssetUrlHelperEmptyModuleTest < ActionView::TestCase
assert @module.config.asset_host
assert_equal "http://www.example.com/foo", @module.asset_url("foo")
end
+
+ def test_asset_url_with_custom_asset_host
+ @module.instance_eval do
+ def config
+ Struct.new(:asset_host).new("http://www.example.com")
+ end
+ end
+
+ assert @module.config.asset_host
+ assert_equal "http://custom.example.com/foo", @module.asset_url("foo", :host => "http://custom.example.com")
+ end
end
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index 73fa3b6b4e..5e991d87ad 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -68,6 +68,23 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=radio][value=false][disabled=disabled]'
end
+ test 'collection radio accepts multiple readonly items' do
+ collection = [[1, true], [0, false], [2, 'other']]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => [true, false]
+
+ assert_select 'input[type=radio][value=true][readonly=readonly]'
+ assert_select 'input[type=radio][value=false][readonly=readonly]'
+ assert_no_select 'input[type=radio][value=other][readonly=readonly]'
+ end
+
+ test 'collection radio accepts single readonly item' do
+ collection = [[1, true], [0, false]]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => true
+
+ assert_select 'input[type=radio][value=true][readonly=readonly]'
+ assert_no_select 'input[type=radio][value=false][readonly=readonly]'
+ end
+
test 'collection radio accepts html options as input' do
collection = [[1, true], [0, false]]
with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio'
@@ -204,6 +221,13 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1
end
+ test 'collection check boxes generates a hidden field with index if it was provided' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name, { index: 322 }
+
+ assert_select "input[type=hidden][name='user[322][category_ids][]'][value=]", count: 1
+ end
+
test 'collection check boxes does not generate a hidden field if include_hidden option is false' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_check_boxes :user, :category_ids, collection, :id, :name, include_hidden: false
@@ -325,6 +349,33 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
end
+ test 'collection check boxes accepts multiple readonly items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => [1, 3]
+
+ assert_select 'input[type=checkbox][value=1][readonly=readonly]'
+ assert_select 'input[type=checkbox][value=3][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=2][readonly=readonly]'
+ end
+
+ test 'collection check boxes accepts single readonly item' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => 1
+
+ assert_select 'input[type=checkbox][value=1][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=3][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=2][readonly=readonly]'
+ end
+
+ test 'collection check boxes accepts a proc to readonly items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => proc { |i| i.first == 1 }
+
+ assert_select 'input[type=checkbox][value=1][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=3][readonly=readonly]'
+ assert_no_select 'input[type=checkbox][value=2][readonly=readonly]'
+ end
+
test 'collection check boxes accepts html options' do
collection = [[1, 'Category 1'], [2, 'Category 2']]
with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check'
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index b5e9801776..7b680aac08 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -19,6 +19,9 @@ class FormHelperTest < ActionView::TestCase
attributes: {
post: {
cost: "Total cost"
+ },
+ :"post/language" => {
+ spanish: "Espanol"
}
}
},
@@ -154,6 +157,12 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_label_with_human_attribute_name_and_options
+ with_locale :label do
+ assert_dom_equal('<label for="post_language_spanish">Espanol</label>', label(:post, :language, value: "spanish"))
+ end
+ end
+
def test_label_with_locales_symbols
with_locale :label do
assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body))
@@ -265,6 +274,13 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_label_with_block_and_html
+ assert_dom_equal(
+ '<label for="post_terms">Accept <a href="/terms">Terms</a>.</label>',
+ label(:post, :terms) { 'Accept <a href="/terms">Terms</a>.'.html_safe }
+ )
+ end
+
def test_label_with_block_and_options
assert_dom_equal(
'<label for="my_for">The title, please:</label>',
@@ -1420,7 +1436,7 @@ class FormHelperTest < ActionView::TestCase
expected = whole_form("/posts", "new_post", "new_post") do
"<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
"<label for='post_1_tag_ids_1'>Tag 1</label>" +
- "<input name='post[tag_ids][]' type='hidden' value='' />"
+ "<input name='post[1][tag_ids][]' type='hidden' value='' />"
end
assert_dom_equal expected, output_buffer
@@ -3020,12 +3036,13 @@ class FormHelperTest < ActionView::TestCase
protected
def hidden_fields(method = nil)
- txt = %{<div style="display:none">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ txt = %{<input name="utf8" type="hidden" value="&#x2713;" />}
+
if method && !%w(get post).include?(method.to_s)
txt << %{<input name="_method" type="hidden" value="#{method}" />}
end
- txt << %{</div>}
+
+ txt
end
def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index cf824e2733..18c739674a 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -14,12 +14,15 @@ class FormTagHelperTest < ActionView::TestCase
method = options[:method]
enforce_utf8 = options.fetch(:enforce_utf8, true)
- txt = %{<div style="display:none">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />} if enforce_utf8
- if method && !%w(get post).include?(method.to_s)
- txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ ''.tap do |txt|
+ if enforce_utf8
+ txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ end
+
+ if method && !%w(get post).include?(method.to_s)
+ txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ end
end
- txt << %{</div>}
end
def form_text(action = "http://www.example.com", options = {})
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index 12d5260a9d..f7c8f36b78 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-# The exhaustive tests are in test/controller/html/sanitizer_test.rb.
+# The exhaustive tests are in test/template/html-scanner/sanitizer_test.rb
# This tests the that the helpers hook up correctly to the sanitizer classes.
class SanitizeHelperTest < ActionView::TestCase
tests ActionView::Helpers::SanitizeHelper
diff --git a/actionview/test/template/test_test.rb b/actionview/test/template/test_test.rb
index 108a674d95..88bac85039 100644
--- a/actionview/test/template/test_test.rb
+++ b/actionview/test/template/test_test.rb
@@ -37,10 +37,22 @@ class PeopleHelperTest < ActionView::TestCase
def test_link_to_person
with_test_route_set do
- person = mock(:name => "David")
- person.class.extend ActiveModel::Naming
- expects(:mocha_mock_path).with(person).returns("/people/1")
+ person = Struct.new(:name) {
+ extend ActiveModel::Naming
+ def to_model; self; end
+ def persisted?; true; end
+ def self.name; 'Mocha::Mock'; end
+ }.new "David"
+
+ the_model = nil
+ extend Module.new {
+ define_method(:mocha_mock_path) { |model, *args|
+ the_model = model
+ "/people/1"
+ }
+ }
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
+ assert_equal person, the_model
end
end
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index c4770840fb..a9d5ea7345 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -151,4 +151,10 @@ class TranslationHelperTest < ActiveSupport::TestCase
translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string'])
assert_equal 'A Generic String', translation
end
+
+ def test_translate_does_not_change_options
+ options = {}
+ translate(:'translations.missing', options)
+ assert_equal({}, options)
+ end
end
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 7e978e15d2..35279a4558 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -53,12 +53,12 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_button_to_with_straight_url
- assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com")
+ assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com")
end
def test_button_to_with_path
assert_dom_equal(
- %{<form method="post" action="/article/Hello" class="button_to"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="/article/Hello" class="button_to"><input type="submit" value="Hello" /></form>},
button_to("Hello", article_path("Hello".html_safe))
)
end
@@ -67,7 +67,7 @@ class UrlHelperTest < ActiveSupport::TestCase
self.request_forgery = true
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></form>},
button_to("Hello", "http://www.example.com")
)
ensure
@@ -75,102 +75,102 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_button_to_with_form_class
- assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class')
+ assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class')
end
def test_button_to_with_form_class_escapes
- assert_dom_equal %{<form method="post" action="http://www.example.com" class="&lt;script&gt;evil_js&lt;/script&gt;"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>')
+ assert_dom_equal %{<form method="post" action="http://www.example.com" class="&lt;script&gt;evil_js&lt;/script&gt;"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>')
end
def test_button_to_with_query
- assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
+ assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
end
def test_button_to_with_html_safe_URL
- assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
+ assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
end
def test_button_to_with_query_and_no_name
- assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&amp;q2=v2" class="button_to"><div><input type="submit" value="http://www.example.com?q1=v1&amp;q2=v2" /></div></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2")
+ assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="http://www.example.com?q1=v1&amp;q2=v2" /></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2")
end
def test_button_to_with_javascript_confirm
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" })
)
end
def test_button_to_with_javascript_disable_with
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", data: { disable_with: "Greeting..." })
)
end
def test_button_to_with_remote_and_form_options
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: true, form: { class: "custom-class", "data-type" => "json" })
)
end
def test_button_to_with_remote_and_javascript_confirm
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: true, data: { confirm: "Are you sure?" })
)
end
def test_button_to_with_remote_and_javascript_disable_with
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: true, data: { disable_with: "Greeting..." })
)
end
def test_button_to_with_remote_false
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", remote: false)
)
end
def test_button_to_enabled_disabled
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", disabled: false)
)
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input disabled="disabled" type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input disabled="disabled" type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", disabled: true)
)
end
def test_button_to_with_method_delete
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", method: :delete)
)
end
def test_button_to_with_method_get
assert_dom_equal(
- %{<form method="get" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>},
+ %{<form method="get" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>},
button_to("Hello", "http://www.example.com", method: :get)
)
end
def test_button_to_with_block
assert_dom_equal(
- %{<form method="post" action="http://www.example.com" class="button_to"><div><button type="submit"><span>Hello</span></button></div></form>},
+ %{<form method="post" action="http://www.example.com" class="button_to"><button type="submit"><span>Hello</span></button></form>},
button_to("http://www.example.com") { content_tag(:span, 'Hello') }
)
end
def test_button_to_with_params
assert_dom_equal(
- %{<form action="http://www.example.com" class="button_to" method="post"><div><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></div></form>},
+ %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></form>},
button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"})
)
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 0db220ab8f..b94558b65c 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,6 +1,11 @@
+* Add plural and singular form for length validator's default messages.
+
+ *Abd ar-Rahman Hamid*
+
* Introduce `validate` as an alias for `valid?`.
- This is more intuitive when you want to run validations but don't care about the return value.
+ This is more intuitive when you want to run validations but don't care about
+ the return value.
*Henrik Nyh*
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 3e49c34182..feb3d9371d 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -48,6 +48,7 @@ module ActiveModel
eager_autoload do
autoload :Errors
+ autoload :StrictValidationFailed, 'active_model/errors'
end
module Serializers
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index 540e8132d3..bf07945fe1 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -14,9 +14,15 @@ en:
empty: "can't be empty"
blank: "can't be blank"
present: "must be blank"
- too_long: "is too long (maximum is %{count} characters)"
- too_short: "is too short (minimum is %{count} characters)"
- wrong_length: "is the wrong length (should be %{count} characters)"
+ too_long:
+ one: "is too long (maximum is 1 character)"
+ other: "is too long (maximum is %{count} characters)"
+ too_short:
+ one: "is too short (minimum is 1 character)"
+ other: "is too short (minimum is %{count} characters)"
+ wrong_length:
+ one: "is the wrong length (should be 1 character)"
+ other: "is the wrong length (should be %{count} characters)"
not_a_number: "is not a number"
not_an_integer: "must be an integer"
greater_than: "must be greater than %{count}"
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 826e89bf9d..4033eb5808 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -52,8 +52,6 @@ module ActiveModel
raise
end
- attr_reader :password
-
include InstanceMethodsOnActivation
if options.fetch(:validations, true)
@@ -68,6 +66,7 @@ module ActiveModel
validates_confirmation_of :password, if: ->{ password.present? }
end
+ # This code is necessary as long as the protected_attributes gem is supported.
if respond_to?(:attributes_protected_by_default)
def self.attributes_protected_by_default #:nodoc:
super + ['password_digest']
@@ -91,6 +90,8 @@ module ActiveModel
BCrypt::Password.new(password_digest) == unencrypted_password && self
end
+ attr_reader :password
+
# Encrypts the password into the +password_digest+ attribute, only if the
# new password is not blank.
#
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 93600c587a..3eeb80a48b 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -72,28 +72,40 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
end
# validates_length_of: generate_message(attr, :too_long, message: custom_message, count: option_value.end)
- def test_generate_message_too_long_with_default_message
+ def test_generate_message_too_long_with_default_message_plural
assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, count: 10)
end
+ def test_generate_message_too_long_with_default_message_singular
+ assert_equal "is too long (maximum is 1 character)", @person.errors.generate_message(:title, :too_long, count: 1)
+ end
+
def test_generate_message_too_long_with_custom_message
assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, message: 'custom message %{count}', count: 10)
end
# validates_length_of: generate_message(attr, :too_short, default: custom_message, count: option_value.begin)
- def test_generate_message_too_short_with_default_message
+ def test_generate_message_too_short_with_default_message_plural
assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, count: 10)
end
+ def test_generate_message_too_short_with_default_message_singular
+ assert_equal "is too short (minimum is 1 character)", @person.errors.generate_message(:title, :too_short, count: 1)
+ end
+
def test_generate_message_too_short_with_custom_message
assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, message: 'custom message %{count}', count: 10)
end
# validates_length_of: generate_message(attr, :wrong_length, message: custom_message, count: option_value)
- def test_generate_message_wrong_length_with_default_message
+ def test_generate_message_wrong_length_with_default_message_plural
assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, count: 10)
end
+ def test_generate_message_wrong_length_with_default_message_singular
+ assert_equal "is the wrong length (should be 1 character)", @person.errors.generate_message(:title, :wrong_length, count: 1)
+ end
+
def test_generate_message_wrong_length_with_custom_message
assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, message: 'custom message %{count}', count: 10)
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 49d134a961..1c85197ec7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,8 +1,351 @@
-* Fixed serialized fields returning serialized data after being updated with
+* Fixed serialized fields returning serialized data after being updated with
`update_column`.
*Simon Hørup Eskildsen*
+* Fixed polymorphic eager loading when using a String as foreign key.
+
+ Fixes #14734.
+
+ *Lauro Caetano*
+
+* Change belongs_to touch to be consistent with timestamp updates
+
+ If a model is set up with a belongs_to: touch relatinoship the parent
+ record will only be touched if the record was modified. This makes it
+ consistent with timestamp updating on the record itself.
+
+ *Brock Trappitt*
+
+* Fixed the inferred table name of a HABTM auxiliar table inside a schema.
+
+ Fixes #14824
+
+ *Eric Chahin*
+
+* Remove unused `:timestamp` type. Transparently alias it to `:datetime`
+ in all cases. Fixes inconsistencies when column types are sent outside of
+ `ActiveRecord`, such as for XML Serialization.
+
+ *Sean Griffin*
+
+* Fix bug that added `table_name_prefix` and `table_name_suffix` to
+ extension names in PostgreSQL when migrating.
+
+ *Joao Carlos*
+
+* The `:index` option in migrations, which previously was only available for
+ `references`, now works with any column types.
+
+ *Marc Schütz*
+
+* Add support for counter name to be passed as parameter on `CounterCache::ClassMethods#reset_counters`.
+
+ *jnormore*
+
+* Restrict deletion of record when using `delete_all` with `uniq`, `group`, `having`
+ or `offset`.
+
+ In these cases the generated query ignored them and that caused unintended
+ records to be deleted.
+
+ Fixes #11985.
+
+ *Leandro Facchinetti*
+
+* Floats with limit >= 25 that get turned into doubles in MySQL no longer have
+ their limit dropped from the schema.
+
+ Fixes #14135.
+
+ *Aaron Nelson*
+
+* Fix how to calculate associated class name when using namespaced `has_and_belongs_to_many`
+ association.
+
+ Fixes #14709.
+
+ *Kassio Borges*
+
+* `ActiveRecord::Relation::Merger#filter_binds` now compares equivalent symbols and
+ strings in column names as equal.
+
+ This fixes a rare case in which more bind values are passed than there are
+ placeholders for them in the generated SQL statement, which can make PostgreSQL
+ throw a `StatementInvalid` exception.
+
+ *Nat Budin*
+
+* Fix `stored_attributes` to correctly merge the details of stored
+ attributes defined in parent classes.
+
+ Fixes #14672.
+
+ *Brad Bennett*, *Jessica Yao*, *Lakshmi Parthasarathy*
+
+* `change_column_default` allows `[]` as argument to `change_column_default`.
+
+ Fixes #11586.
+
+ *Yves Senn*
+
+* Handle `name` and `"char"` column types in the PostgreSQL adapter.
+
+ `name` and `"char"` are special character types used internally by
+ PostgreSQL and are used by internal system catalogs. These field types
+ can sometimes show up in structure-sniffing queries that feature internal system
+ structures or with certain PostgreSQL extensions.
+
+ *J Smith*, *Yves Senn*
+
+* Fix `PostgreSQLAdapter::OID::Float#type_cast` to convert Infinity and
+ NaN PostgreSQL values into a native Ruby `Float::INFINITY` and `Float::NAN`
+
+ Before:
+
+ Point.create(value: 1.0/0)
+ Point.last.value # => 0.0
+
+ After:
+
+ Point.create(value: 1.0/0)
+ Point.last.value # => Infinity
+
+ *Innokenty Mikhailov*
+
+* Allow the PostgreSQL adapter to handle bigserial pk types again.
+
+ Fixes #10410.
+
+ *Patrick Robertson*
+
+* Deprecate joining, eager loading and preloading of instance dependent
+ associations without replacement. These operations happen before instances
+ are created. The current behavior is unexpected and can result in broken
+ behavior.
+
+ Fixes #15024.
+
+ *Yves Senn*
+
+* Fixed HABTM's CollectionAssociation size calculation.
+
+ HABTM should fall back to using the normal CollectionAssociation's size
+ calculation if the collection is not cached or loaded.
+
+ Fixes #14913, #14914.
+
+ *Fred Wu*
+
+* Return a non zero status when running `rake db:migrate:status` and migration table does
+ not exist.
+
+ *Paul B.*
+
+* Add support for module-level `table_name_suffix` in models.
+
+ This makes `table_name_suffix` work the same way as `table_name_prefix` when
+ using namespaced models.
+
+ *Jenner LaFave*
+
+* Revert the behaviour of `ActiveRecord::Relation#join` changed through 4.0 => 4.1 to 4.0.
+
+ In 4.1.0 `Relation#join` is delegated to `Arel#SelectManager`.
+ In 4.0 series it is delegated to `Array#join`.
+
+ *Bogdan Gusiev*
+
+* Log nil binary column values correctly.
+
+ When an object with a binary column is updated with a nil value
+ in that column, the SQL logger would throw an exception when trying
+ to log that nil value. This only occurs when updating a record
+ that already has a non-nil value in that column since an initial nil
+ value isn't included in the SQL anyway (at least, when dirty checking
+ is enabled.) The column's new value will now be logged as `<NULL binary data>`
+ to parallel the existing `<N bytes of binary data>` for non-nil values.
+
+ *James Coleman*
+
+* Rails will now pass a custom validation context through to autosave associations
+ in order to validate child associations with the same context.
+
+ Fixes #13854.
+
+ *Eric Chahin*, *Aaron Nelson*, *Kevin Casey*
+
+* Stringify all variable keys of mysql connection configuration.
+
+ When the `sql_mode` variable for mysql adapters is set in the configuration
+ as a `String`, it was ignored and overwritten by the strict mode option.
+
+ Fixes #14895.
+
+ *Paul Nikitochkin*
+
+* Ensure SQLite3 statements are closed on errors.
+
+ Fixes #13631.
+
+ *Timur Alperovich*
+
+* Give ActiveRecord::PredicateBuilder private methods the privacy they deserve.
+
+ *Hector Satre*
+
+* When using a custom `join_table` name on a `habtm`, rails was not saving it
+ on Reflections. This causes a problem when rails loads fixtures, because it
+ uses the reflections to set database with fixtures.
+
+ Fixes #14845.
+
+ *Kassio Borges*
+
+* Reset the cache when modifying a Relation with cached Arel.
+ Additionally display a warning message to make the user aware.
+
+ *Yves Senn*
+
+* PostgreSQL should internally use `:datetime` consistently for TimeStamp. Assures
+ different spellings of timestamps are treated the same.
+
+ Example:
+
+ mytimestamp.simplified_type('timestamp without time zone')
+ # => :datetime
+ mytimestamp.simplified_type('timestamp(6) without time zone')
+ # => also :datetime (previously would be :timestamp)
+
+ See #14513.
+
+ *Jefferson Lai*
+
+* `ActiveRecord::Base.no_touching` no longer triggers callbacks or start empty transactions.
+
+ Fixes #14841.
+
+ *Lucas Mazza*
+
+* Fix name collision with `Array#select!` with `Relation#select!`.
+
+ Fixes #14752.
+
+ *Earl St Sauver*
+
+* Fixed unexpected behavior for `has_many :through` associations going through a scoped `has_many`.
+
+ If a `has_many` association is adjusted using a scope, and another `has_many :through`
+ uses this association, then the scope adjustment is unexpectedly neglected.
+
+ Fixes #14537.
+
+ *Jan Habermann*
+
+* `@destroyed` should always be set to `false` when an object is duped.
+
+ *Kuldeep Aggarwal*
+
+* Fixed has_many association to make it support irregular inflections.
+
+ Fixes #8928.
+
+ *arthurnn*, *Javier Goizueta*
+
+* Fixed a problem where count used with a grouping was not returning a Hash.
+
+ Fixes #14721.
+
+ *Eric Chahin*
+
+* `sanitize_sql_like` helper method to escape a string for safe use in a SQL
+ LIKE statement.
+
+ Example:
+
+ class Article
+ def self.search(term)
+ where("title LIKE ?", sanitize_sql_like(term))
+ end
+ end
+
+ Article.search("20% _reduction_")
+ # => Query looks like "... title LIKE '20\% \_reduction\_' ..."
+
+ *Rob Gilson*, *Yves Senn*
+
+* Do not quote uuid default value on `change_column`.
+
+ Fixes #14604.
+
+ *Eric Chahin*
+
+* The comparison between `Relation` and `CollectionProxy` should be consistent.
+
+ Example:
+
+ author.posts == Post.where(author_id: author.id)
+ # => true
+ Post.where(author_id: author.id) == author.posts
+ # => true
+
+ Fixes #13506.
+
+ *Lauro Caetano*
+
+* Calling `delete_all` on an unloaded `CollectionProxy` no longer
+ generates a SQL statement containing each id of the collection:
+
+ Before:
+
+ DELETE FROM `model` WHERE `model`.`parent_id` = 1
+ AND `model`.`id` IN (1, 2, 3...)
+
+ After:
+
+ DELETE FROM `model` WHERE `model`.`parent_id` = 1
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
+* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select`
+ which created invalid SQL.
+
+ Fixes #13648.
+
+ *Simon Woker*
+
+* PostgreSQL adapter only warns once for every missing OID per connection.
+
+ Fixes #14275.
+
+ *Matthew Draper*, *Yves Senn*
+
+* PostgreSQL adapter automatically reloads it's type map when encountering
+ unknown OIDs.
+
+ Fixes #14678.
+
+ *Matthew Draper*, *Yves Senn*
+
+* Fix insertion of records via `has_many :through` association with scope.
+
+ Fixes #3548.
+
+ *Ivan Antropov*
+
+* Auto-generate stable fixture UUIDs on PostgreSQL.
+
+ Fixes #11524.
+
+ *Roderick van Domburg*
+
+* Fixed a problem where an enum would overwrite values of another enum
+ with the same name in an unrelated class.
+
+ Fixes #14607.
+
+ *Evan Whalen*
+
* PostgreSQL and SQLite string columns no longer have a default limit of 255.
Fixes #13435, #9153.
@@ -110,12 +453,6 @@
*Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca*
-* Save `has_one` association even if the record doesn't changed.
-
- Fixes #14407.
-
- *Rafael Mendonça França*
-
* Use singular table name in generated migrations when
`ActiveRecord::Base.pluralize_table_names` is `false`.
@@ -194,6 +531,12 @@
*Troy Kruthoff*, *Lachlan Sylvester*
+* Only save has_one associations if record has changes.
+ Previously after save related callbacks, such as `#after_commit`, were triggered when the has_one
+ object did not get saved to the db.
+
+ *Alan Kennedy*
+
* Allow strings to specify the `#order` value.
Example:
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index e04abe9b37..e5b68750e4 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -1,4 +1,4 @@
-= Active Record -- Object-relational mapping put on rails
+= Active Record -- Object-relational mapping in Rails
Active Record connects classes to relational database tables to establish an
almost zero-configuration persistence layer for applications. The library
@@ -19,9 +19,11 @@ A short rundown of some of the major features:
class Product < ActiveRecord::Base
end
-
- The Product class is automatically mapped to the table named "products",
- which might look like this:
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+The Product class is automatically mapped to the table named "products",
+which might look like this:
CREATE TABLE products (
id int(11) NOT NULL auto_increment,
@@ -29,11 +31,9 @@ A short rundown of some of the major features:
PRIMARY KEY (id)
);
- This would also define the following accessors: `Product#name` and
- `Product#name=(new_name)`
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
+This would also define the following accessors: `Product#name` and
+`Product#name=(new_name)`.
+
* Associations between objects defined by simple class methods.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 6f8948f987..7769966a22 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -38,33 +38,36 @@ namespace :test do
end
end
+desc 'Build MySQL and PostgreSQL test databases'
namespace :db do
- desc 'Build MySQL and PostgreSQL test databases'
- task create: ['mysql:build_databases', 'postgresql:build_databases']
- desc 'Drop MySQL and PostgreSQL test databases'
- task drop: ['mysql:drop_databases', 'postgresql:drop_databases']
+ task :create => ['db:mysql:build', 'db:postgresql:build']
+ task :drop => ['db:mysql:drop', 'db:postgresql:drop']
end
-%w( mysql mysql2 postgresql sqlite3 sqlite3_mem firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
- Rake::TestTask.new("test_#{adapter}") { |t|
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
- t.libs << 'test'
- t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
- |x| x =~ /\/adapters\//
- } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
-
- t.warning = true
- t.verbose = true
- }
-
- task "isolated_test_#{adapter}" do
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
- puts [adapter, adapter_short].inspect
- (Dir["test/cases/**/*_test.rb"].reject {
- |x| x =~ /\/adapters\//
- } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
- sh(Gem.ruby, '-w' ,"-Itest", file)
- end or raise "Failures"
+%w( mysql mysql2 postgresql sqlite3 sqlite3_mem db2 oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
+ namespace :test do
+ Rake::TestTask.new(adapter => "#{adapter}:env") { |t|
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
+ t.libs << 'test'
+ t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
+ |x| x =~ /\/adapters\//
+ } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
+
+ t.warning = true
+ t.verbose = true
+ }
+
+ namespace :isolated do
+ task adapter => "#{adapter}:env" do
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
+ puts [adapter, adapter_short].inspect
+ (Dir["test/cases/**/*_test.rb"].reject {
+ |x| x =~ /\/adapters\//
+ } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
+ sh(Gem.ruby, '-w' ,"-Itest", file)
+ end or raise "Failures"
+ end
+ end
end
namespace adapter do
@@ -76,8 +79,8 @@ end
end
# Make sure the adapter test evaluates the env setting task
- task "test_#{adapter}" => "#{adapter}:env"
- task "isolated_test_#{adapter}" => "#{adapter}:env"
+ task "test_#{adapter}" => ["#{adapter}:env", "test:#{adapter}"]
+ task "isolated_test_#{adapter}" => ["#{adapter}:env", "test:isolated:#{adapter}"]
end
rule '.sqlite3' do |t|
@@ -89,109 +92,58 @@ task :test_sqlite3 => [
'test/fixtures/fixture_database_2.sqlite3'
]
-namespace :mysql do
- desc 'Build the MySQL test databases'
- task :build_databases do
- config = ARTest.config['connections']['mysql']
- %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
- %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
- end
-
- desc 'Drop the MySQL test databases'
- task :drop_databases do
- config = ARTest.config['connections']['mysql']
- %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} )
- %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} )
- end
-
- desc 'Rebuild the MySQL test databases'
- task :rebuild_databases => [:drop_databases, :build_databases]
-end
-
-task :build_mysql_databases => 'mysql:build_databases'
-task :drop_mysql_databases => 'mysql:drop_databases'
-task :rebuild_mysql_databases => 'mysql:rebuild_databases'
-
-
-namespace :postgresql do
- desc 'Build the PostgreSQL test databases'
- task :build_databases do
- config = ARTest.config['connections']['postgresql']
- %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} )
- %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} )
+namespace :db do
+ namespace :mysql do
+ desc 'Build the MySQL test databases'
+ task :build do
+ config = ARTest.config['connections']['mysql']
+ %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
+ %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ")
+ end
- # notify about preparing hstore
- if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0"
- puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
+ desc 'Drop the MySQL test databases'
+ task :drop do
+ config = ARTest.config['connections']['mysql']
+ %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} )
+ %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} )
end
- end
- desc 'Drop the PostgreSQL test databases'
- task :drop_databases do
- config = ARTest.config['connections']['postgresql']
- %x( dropdb #{config['arunit']['database']} )
- %x( dropdb #{config['arunit2']['database']} )
+ desc 'Rebuild the MySQL test databases'
+ task :rebuild => [:drop, :build]
end
- desc 'Rebuild the PostgreSQL test databases'
- task :rebuild_databases => [:drop_databases, :build_databases]
-end
-
-task :build_postgresql_databases => 'postgresql:build_databases'
-task :drop_postgresql_databases => 'postgresql:drop_databases'
-task :rebuild_postgresql_databases => 'postgresql:rebuild_databases'
-
-
-namespace :frontbase do
- desc 'Build the FrontBase test databases'
- task :build_databases => :rebuild_frontbase_databases
-
- desc 'Rebuild the FrontBase test databases'
- task :rebuild_databases do
- build_frontbase_database = Proc.new do |db_name, sql_definition_file|
- %(
- STOP DATABASE #{db_name};
- DELETE DATABASE #{db_name};
- CREATE DATABASE #{db_name};
+ namespace :postgresql do
+ desc 'Build the PostgreSQL test databases'
+ task :build do
+ config = ARTest.config['connections']['postgresql']
+ %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} )
+ %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} )
- CONNECT TO #{db_name} AS SESSION_NAME USER _SYSTEM;
- SET COMMIT FALSE;
-
- CREATE USER RAILS;
- CREATE SCHEMA RAILS AUTHORIZATION RAILS;
- COMMIT;
-
- SET SESSION AUTHORIZATION RAILS;
- SCRIPT '#{sql_definition_file}';
-
- COMMIT;
-
- DISCONNECT ALL;
- )
+ # prepare hstore
+ if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0"
+ puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
+ end
end
- config = ARTest.config['connections']['frontbase']
- create_activerecord_unittest = build_frontbase_database[config['arunit']['database'], File.join(SCHEMA_ROOT, 'frontbase.sql')]
- create_activerecord_unittest2 = build_frontbase_database[config['arunit2']['database'], File.join(SCHEMA_ROOT, 'frontbase2.sql')]
- execute_frontbase_sql = Proc.new do |sql|
- system(<<-SHELL)
- /Library/FrontBase/bin/sql92 <<-SQL
- #{sql}
- SQL
- SHELL
+
+ desc 'Drop the PostgreSQL test databases'
+ task :drop do
+ config = ARTest.config['connections']['postgresql']
+ %x( dropdb #{config['arunit']['database']} )
+ %x( dropdb #{config['arunit2']['database']} )
end
- execute_frontbase_sql[create_activerecord_unittest]
- execute_frontbase_sql[create_activerecord_unittest2]
+
+ desc 'Rebuild the PostgreSQL test databases'
+ task :rebuild => [:drop, :build]
end
end
-task :build_frontbase_databases => 'frontbase:build_databases'
-task :rebuild_frontbase_databases => 'frontbase:rebuild_databases'
+task :build_mysql_databases => 'db:mysql:build'
+task :drop_mysql_databases => 'db:mysql:drop'
+task :rebuild_mysql_databases => 'db:mysql:rebuild'
-spec = eval(File.read('activerecord.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
+task :build_postgresql_databases => 'db:postgresql:build'
+task :drop_postgresql_databases => 'db:postgresql:drop'
+task :rebuild_postgresql_databases => 'db:postgresql:rebuild'
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
@@ -217,6 +169,11 @@ task :lines do
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
end
+spec = eval(File.read('activerecord.gemspec'))
+
+Gem::PackageTask.new(spec) do |p|
+ p.gem_spec = spec
+end
# Publishing ------------------------------------------------------
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index d397c9e016..8075008574 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '~> 5.0.0'
+ s.add_dependency 'arel', '~> 6.0.0'
end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 0d5313956b..45c275a017 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -230,8 +230,8 @@ module ActiveRecord
private
def reader_method(name, class_name, mapping, allow_nil, constructor)
define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? })
+ attrs = mapping.collect {|key, _| read_attribute(key)}
object = constructor.respond_to?(:call) ?
constructor.call(*attrs) :
class_name.constantize.send(constructor, *attrs)
@@ -249,10 +249,10 @@ module ActiveRecord
end
if part.nil? && allow_nil
- mapping.each { |pair| self[pair.first] = nil }
+ mapping.each { |key, _| self[key] = nil }
@aggregation_cache[name] = nil
else
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
+ mapping.each { |key, value| self[key] = part.send(value) }
@aggregation_cache[name] = part.freeze
end
end
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 20516bba0c..5a84792f45 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -9,6 +9,10 @@ module ActiveRecord
@association
end
+ def ==(other)
+ other == to_a
+ end
+
private
def exec_queries
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 4abe2ad0a0..727ee5f65f 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -419,6 +419,10 @@ module ActiveRecord
# has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event'
# end
#
+ # Note: Joining, eager loading and preloading of these associations is not fully possible.
+ # These operations happen before instance creation and the scope will be called with a +nil+ argument.
+ # This can lead to unexpected behavior and is deprecated.
+ #
# == Association callbacks
#
# Similar to the normal callbacks that hook into the life cycle of an Active Record object,
@@ -773,11 +777,16 @@ module ActiveRecord
# like this can have unintended consequences.
# In the above example posts with no approved comments are not returned at all, because
# the conditions apply to the SQL statement as a whole and not just to the association.
+ #
# You must disambiguate column references for this fallback to happen, for example
# <tt>order: "author.name DESC"</tt> will work but <tt>order: "name DESC"</tt> will not.
#
- # If you do want eager load only some members of an association it is usually more natural
- # to include an association which has conditions defined on it:
+ # If you want to load all posts (including posts with no approved comments) then write
+ # your own LEFT OUTER JOIN query using ON
+ #
+ # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
+ #
+ # In this case it is usually more natural to include an association which has conditions defined on it:
#
# class Post < ActiveRecord::Base
# has_many :approved_comments, -> { where approved: true }, class_name: 'Comment'
@@ -1571,6 +1580,11 @@ module ActiveRecord
join_model = builder.through_model
+ # FIXME: we should move this to the internal constants. Also people
+ # should never directly access this constant so I'm not happy about
+ # setting it.
+ const_set join_model.name, join_model
+
middle_reflection = builder.middle_reflection join_model
Builder::HasMany.define_callbacks self, middle_reflection
@@ -1590,7 +1604,7 @@ module ActiveRecord
hm_options[:through] = middle_reflection.name
hm_options[:source] = join_model.right_reflection.name
- [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate].each do |k|
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table].each do |k|
hm_options[k] = options[k] if options.key? k
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 27fd9e35db..572f556999 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -1,12 +1,34 @@
module ActiveRecord
module Associations
class AssociationScope #:nodoc:
- INSTANCE = new
-
def self.scope(association, connection)
INSTANCE.scope association, connection
end
+ class BindSubstitution
+ def initialize(block)
+ @block = block
+ end
+
+ def bind_value(scope, column, value, alias_tracker)
+ substitute = alias_tracker.connection.substitute_at(
+ column, scope.bind_values.length)
+ scope.bind_values += [[column, @block.call(value)]]
+ substitute
+ end
+ end
+
+ def self.create(&block)
+ block = block ? block : lambda { |val| val }
+ new BindSubstitution.new(block)
+ end
+
+ def initialize(bind_substitution)
+ @bind_substitution = bind_substitution
+ end
+
+ INSTANCE = create
+
def scope(association, connection)
klass = association.klass
reflection = association.reflection
@@ -22,6 +44,23 @@ module ActiveRecord
Arel::Nodes::InnerJoin
end
+ def self.get_bind_values(owner, chain)
+ bvs = []
+ chain.each_with_index do |reflection, i|
+ if reflection == chain.last
+ bvs << reflection.join_id_for(owner)
+ if reflection.type
+ bvs << owner.class.base_class.name
+ end
+ else
+ if reflection.type
+ bvs << chain[i + 1].klass.base_class.name
+ end
+ end
+ end
+ bvs
+ end
+
private
def construct_tables(chain, klass, refl, alias_tracker)
@@ -49,10 +88,7 @@ module ActiveRecord
end
def bind_value(scope, column, value, alias_tracker)
- substitute = alias_tracker.connection.substitute_at(
- column, scope.bind_values.length)
- scope.bind_values += [[column, value]]
- substitute
+ @bind_substitution.bind_value scope, column, value, alias_tracker
end
def bind(scope, table_name, column_name, value, tracker)
@@ -120,6 +156,7 @@ module ActiveRecord
end
scope.where_values += item.where_values
+ scope.bind_values += item.bind_values
scope.order_values |= item.order_values
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 8272a5584c..1edd4fa3aa 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -31,6 +31,14 @@ module ActiveRecord
@updated
end
+ def decrement_counters # :nodoc:
+ with_cache_name { |name| decrement_counter name }
+ end
+
+ def increment_counters # :nodoc:
+ with_cache_name { |name| increment_counter name }
+ end
+
private
def find_target?
@@ -51,13 +59,15 @@ module ActiveRecord
end
end
- def decrement_counters
- with_cache_name { |name| decrement_counter name }
+ def decrement_counter(counter_cache_name)
+ if foreign_key_present?
+ klass.decrement_counter(counter_cache_name, target_id)
+ end
end
- def decrement_counter counter_cache_name
+ def increment_counter(counter_cache_name)
if foreign_key_present?
- klass.decrement_counter(counter_cache_name, target_id)
+ klass.increment_counter(counter_cache_name, target_id)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 5ccaa55a32..3998aca23e 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -26,28 +26,9 @@ module ActiveRecord::Associations::Builder
private
def self.add_counter_cache_methods(mixin)
- return if mixin.method_defined? :belongs_to_counter_cache_after_create
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
mixin.class_eval do
- def belongs_to_counter_cache_after_create(reflection)
- if record = send(reflection.name)
- cache_column = reflection.counter_cache_column
- record.class.increment_counter(cache_column, record.id)
- @_after_create_counter_called = true
- end
- end
-
- def belongs_to_counter_cache_before_destroy(reflection)
- foreign_key = reflection.foreign_key.to_sym
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
- record = send reflection.name
- if record && !self.destroyed?
- cache_column = reflection.counter_cache_column
- record.class.decrement_counter(cache_column, record.id)
- end
- end
- end
-
def belongs_to_counter_cache_after_update(reflection)
foreign_key = reflection.foreign_key
cache_column = reflection.counter_cache_column
@@ -73,14 +54,6 @@ module ActiveRecord::Associations::Builder
def self.add_counter_cache_callbacks(model, reflection)
cache_column = reflection.counter_cache_column
- model.after_create lambda { |record|
- record.belongs_to_counter_cache_after_create(reflection)
- }
-
- model.before_destroy lambda { |record|
- record.belongs_to_counter_cache_before_destroy(reflection)
- }
-
model.after_update lambda { |record|
record.belongs_to_counter_cache_after_update(reflection)
}
@@ -130,7 +103,7 @@ module ActiveRecord::Associations::Builder
BelongsTo.touch_record(record, foreign_key, n, touch)
}
- model.after_save callback
+ model.after_save callback, if: :changed?
model.after_touch callback
model.after_destroy callback
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index e472277374..30b11c01eb 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -11,7 +11,7 @@ module ActiveRecord::Associations::Builder
end
def join_table
- @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
end
private
@@ -23,7 +23,13 @@ module ActiveRecord::Associations::Builder
KnownTable.new options[:join_table].to_s
else
class_name = options.fetch(:class_name) {
- name.to_s.camelize.singularize
+ model_name = name.to_s.camelize.singularize
+
+ if lhs_class.parent_name
+ model_name.prepend("#{lhs_class.parent_name}::")
+ end
+
+ model_name
}
KnownClass.new lhs_class, class_name
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index 7909b93622..4c8c826f76 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def valid_options
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index dee7e972c1..c5f7bcae7d 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -182,11 +182,11 @@ module ActiveRecord
#
# See delete for more info.
def delete_all(dependent = nil)
- if dependent.present? && ![:nullify, :delete_all].include?(dependent)
+ if dependent && ![:nullify, :delete_all].include?(dependent)
raise ArgumentError, "Valid values are :nullify or :delete_all"
end
- dependent = if dependent.present?
+ dependent = if dependent
dependent
elsif options[:dependent] == :destroy
:delete_all
@@ -194,7 +194,7 @@ module ActiveRecord
options[:dependent]
end
- delete(:all, dependent: dependent).tap do
+ delete_or_nullify_all_records(dependent).tap do
reset
loaded!
end
@@ -244,19 +244,12 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
+ return if records.empty?
_options = records.extract_options!
dependent = _options[:dependent] || options[:dependent]
- if records.first == :all
- if (loaded? || dependent == :destroy) && dependent != :delete_all
- delete_or_destroy(load_target, dependent)
- else
- delete_records(:all, dependent)
- end
- else
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
- delete_or_destroy(records, dependent)
- end
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ delete_or_destroy(records, dependent)
end
# Deletes the +records+ and removes them from this association calling
@@ -265,6 +258,7 @@ module ActiveRecord
# Note that this method removes records from the database ignoring the
# +:dependent+ option.
def destroy(*records)
+ return if records.empty?
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
end
@@ -412,9 +406,23 @@ module ActiveRecord
end
private
+ def get_records
+ return scope.to_a if reflection.scope_chain.any?(&:any?)
+
+ conn = klass.connection
+ sc = reflection.association_scope_cache(conn, owner) do
+ StatementCache.create(conn) { |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge as.scope(self, conn)
+ }
+ end
+
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
+ sc.execute binds, klass, klass.connection
+ end
def find_target
- records = scope.to_a
+ records = get_records
records.each { |record| set_inverse_instance(record) }
records
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 5b71ed163e..84c8cfe72b 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -357,7 +357,7 @@ module ActiveRecord
# Deletes all the records from the collection. For +has_many+ associations,
# the deletion is done according to the strategy specified by the <tt>:dependent</tt>
- # option. Returns an array with the deleted records.
+ # option.
#
# If no <tt>:dependent</tt> option is given, then it will follow the
# default strategy. The default strategy is <tt>:nullify</tt>. This
@@ -435,11 +435,6 @@ module ActiveRecord
# # ]
#
# person.pets.delete_all
- # # => [
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
- # # ]
#
# Pet.find(1, 2, 3)
# # => ActiveRecord::RecordNotFound
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 3e4b7902c0..f5e911c739 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -58,7 +58,7 @@ module ActiveRecord
# the loaded flag is set to true as well.
def count_records
count = if has_cached_counter?
- owner.send(:read_attribute, cached_counter_attribute_name)
+ owner.read_attribute cached_counter_attribute_name
else
scope.count
end
@@ -105,23 +105,27 @@ module ActiveRecord
}
end
+ def delete_count(method, scope)
+ if method == :delete_all
+ scope.delete_all
+ else
+ scope.update_all(reflection.foreign_key => nil)
+ end
+ end
+
+ def delete_or_nullify_all_records(method)
+ count = delete_count(method, self.scope)
+ update_counter(-count)
+ end
+
# Deletes the records according to the <tt>:dependent</tt> option.
def delete_records(records, method)
if method == :destroy
records.each(&:destroy!)
update_counter(-records.length) unless inverse_updates_counter_cache?
else
- if records == :all || !reflection.klass.primary_key
- scope = self.scope
- else
- scope = self.scope.where(reflection.klass.primary_key => records)
- end
-
- if method == :delete_all
- update_counter(-scope.delete_all)
- else
- update_counter(-scope.update_all(reflection.foreign_key => nil))
- end
+ scope = self.scope.where(reflection.klass.primary_key => records)
+ update_counter(-delete_count(method, scope))
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 64bc98c642..35ad512537 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -12,17 +12,18 @@ module ActiveRecord
@through_association = nil
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
- # SELECT query if you use #length.
+ # Returns the size of the collection by executing a SELECT COUNT(*) query
+ # if the collection hasn't been loaded, and by calling collection.size if
+ # it has. If the collection will likely have a size greater than zero,
+ # and if fetching the collection will be needed afterwards, one less
+ # SELECT query will be generated by using #length instead.
def size
if has_cached_counter?
- owner.send(:read_attribute, cached_counter_attribute_name)
+ owner.read_attribute cached_counter_attribute_name(reflection)
elsif loaded?
target.size
else
- count
+ super
end
end
@@ -72,23 +73,25 @@ module ActiveRecord
@through_association ||= owner.association(through_reflection.name)
end
- # We temporarily cache through record that has been build, because if we build a
- # through record in build_record and then subsequently call insert_record, then we
- # want to use the exact same object.
+ # The through record (built with build_record) is temporarily cached
+ # so that it may be reused if insert_record is subsequently called.
#
- # However, after insert_record has been called, we clear the cache entry because
- # we want it to be possible to have multiple instances of the same record in an
- # association
+ # However, after insert_record has been called, the cache is cleared in
+ # order to allow multiple instances of the same record in an association.
def build_through_record(record)
@through_records[record.object_id] ||= begin
ensure_mutable
- through_record = through_association.build
+ through_record = through_association.build through_scope_attributes
through_record.send("#{source_reflection.name}=", record)
through_record
end
end
+ def through_scope_attributes
+ scope.where_values_hash(through_association.reflection.name.to_s)
+ end
+
def save_through_record(record)
build_through_record(record).save!
ensure
@@ -127,13 +130,13 @@ module ActiveRecord
end
end
+ def delete_or_nullify_all_records(method)
+ delete_records(load_target, method)
+ end
+
def delete_records(records, method)
ensure_not_nested
- # This is unoptimised; it will load all the target records
- # even when we just want to delete everything.
- records = load_target if records == :all
-
scope = through_association.scope
scope.where! construct_join_attributes(*records)
@@ -198,7 +201,7 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- scope.to_a
+ get_records
end
# NOTE - not sure that we can actually cope with inverses here
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 94f69d4c2d..5842be3a7b 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -164,17 +164,17 @@ module ActiveRecord
def make_outer_joins(parent, child)
tables = table_aliases_for(parent, child)
join_type = Arel::Nodes::OuterJoin
- joins = make_constraints parent, child, tables, join_type
+ info = make_constraints parent, child, tables, join_type
- joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
+ [info] + child.children.flat_map { |c| make_outer_joins(child, c) }
end
def make_inner_joins(parent, child)
tables = child.tables
join_type = Arel::Nodes::InnerJoin
- joins = make_constraints parent, child, tables, join_type
+ info = make_constraints parent, child, tables, join_type
- joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
+ [info] + child.children.flat_map { |c| make_inner_joins(child, c) }
end
def table_aliases_for(parent, node)
@@ -215,6 +215,7 @@ module ActiveRecord
associations.map do |name, right|
reflection = find_reflection base_klass, name
reflection.check_validity!
+ reflection.check_eager_loadable!
if reflection.options[:polymorphic]
raise EagerLoadPolymorphicError.new(reflection)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 1d923ecc09..a0e83c0a02 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -21,8 +21,11 @@ module ActiveRecord
super && reflection == other.reflection
end
+ JoinInformation = Struct.new :joins, :binds
+
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
joins = []
+ bind_values = []
tables = tables.reverse
scope_chain_index = 0
@@ -60,21 +63,27 @@ module ActiveRecord
left.merge right
end
- if reflection.type
- constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
- end
-
if rel && !rel.arel.constraints.empty?
+ bind_values.concat rel.bind_values
constraint = constraint.and rel.arel.constraints
end
+ if reflection.type
+ value = foreign_klass.base_class.name
+ column = klass.columns_hash[column.to_s]
+
+ substitute = klass.connection.substitute_at(column, bind_values.length)
+ bind_values.push [column, value]
+ constraint = constraint.and table[reflection.type].eq substitute
+ end
+
joins << table.create_join(table, table.create_on(constraint), join_type)
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, klass
end
- joins
+ JoinInformation.new joins, bind_values
end
# Builds equality condition.
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 31ddf4e0fc..42571d6af0 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -80,7 +80,7 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
- NULL_RELATION = Struct.new(:values).new({})
+ NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
@@ -112,13 +112,14 @@ module ActiveRecord
end
def preloaders_for_hash(association, records, scope)
- parent, child = association.to_a.first # hash should only be of length 1
+ association.flat_map { |parent, child|
+ loaders = preloaders_for_one parent, records, scope
- loaders = preloaders_for_one parent, records, scope
-
- recs = loaders.flat_map(&:preloaded_records).uniq
- loaders.concat Array.wrap(child).flat_map { |assoc|
- preloaders_on assoc, recs, scope
+ recs = loaders.flat_map(&:preloaded_records).uniq
+ loaders.concat Array.wrap(child).flat_map { |assoc|
+ preloaders_on assoc, recs, scope
+ }
+ loaders
}
end
@@ -175,6 +176,7 @@ module ActiveRecord
if owners.first.association(reflection.name).loaded?
return AlreadyLoaded
end
+ reflection.check_preloadable!
case reflection.macro
when :has_many
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 69b65982b3..63773bd5e1 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -57,9 +57,15 @@ module ActiveRecord
end
def owners_by_key
- @owners_by_key ||= owners.group_by do |owner|
- owner[owner_key_name]
- end
+ @owners_by_key ||= if key_conversion_required?
+ owners.group_by do |owner|
+ owner[owner_key_name].to_s
+ end
+ else
+ owners.group_by do |owner|
+ owner[owner_key_name]
+ end
+ end
end
def options
@@ -93,13 +99,28 @@ module ActiveRecord
records_by_owner
end
+ def key_conversion_required?
+ association_key_type != owner_key_type
+ end
+
+ def association_key_type
+ @klass.column_types[association_key_name.to_s].type
+ end
+
+ def owner_key_type
+ @model.column_types[owner_key_name.to_s].type
+ end
+
def load_slices(slices)
@preloaded_records = slices.flat_map { |slice|
records_for(slice)
}
@preloaded_records.map { |record|
- [record, record[association_key_name]]
+ key = record[association_key_name]
+ key = key.to_s if key_conversion_required?
+
+ [record, key]
}
end
@@ -111,12 +132,15 @@ module ActiveRecord
scope = klass.unscoped
values = reflection_scope.values
+ reflection_binds = reflection_scope.bind_values
preload_values = preload_scope.values
+ preload_binds = preload_scope.bind_values
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
+ scope.bind_values = (reflection_binds + preload_binds)
- scope.select! preload_values[:select] || values[:select] || table[Arel.star]
+ scope._select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
if preload_values.key? :order
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 70e97432e4..1fed7f74e7 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -84,7 +84,7 @@ module ActiveRecord
end
scope.references! reflection_scope.values[:references]
- scope.order! reflection_scope.values[:order] if scope.eager_loading?
+ scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
end
scope
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 747bb5f1d6..f2e3a4e40f 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -38,8 +38,23 @@ module ActiveRecord
scope.scope_for_create.stringify_keys.except(klass.primary_key)
end
+ def get_records
+ return scope.limit(1).to_a if reflection.scope_chain.any?(&:any?)
+
+ conn = klass.connection
+ sc = reflection.association_scope_cache(conn, owner) do
+ StatementCache.create(conn) { |params|
+ as = AssociationScope.create { params.bind }
+ target_scope.merge(as.scope(self, conn)).limit(1)
+ }
+ end
+
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
+ sc.execute binds, klass, klass.connection
+ end
+
def find_target
- if record = scope.take
+ if record = get_records.first
set_inverse_instance record
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index ba7d2a3782..f8a85b8a6f 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,9 +14,11 @@ module ActiveRecord
def target_scope
scope = super
chain.drop(1).each do |reflection|
+ relation = reflection.klass.all
+ relation.merge!(reflection.scope) if reflection.scope
+
scope.merge!(
- reflection.klass.all.
- except(:select, :create_with, :includes, :preload, :joins, :eager_load)
+ relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
end
scope
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 30fa2c8ba5..816fb51942 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -11,6 +11,15 @@ module ActiveRecord
# If the passed hash responds to <tt>permitted?</tt> method and the return value
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
+ #
+ # cat = Cat.new(name: "Gorby", status: "yawning")
+ # cat.attributes # => { "name" => "Gorby", "status" => "yawning" }
+ # cat.assign_attributes(status: "sleeping")
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping" }
+ #
+ # New attributes will be persisted in the database when the object is saved.
+ #
+ # Aliased to <tt>attributes=</tt>.
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
@@ -140,7 +149,7 @@ module ActiveRecord
end
def read_time
- # If column is a :time (and not :date or :timestamp) there is no need to validate if
+ # If column is a :time (and not :date or :datetime) there is no need to validate if
# there are year/month/day fields
if column.type == :time
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 4b1733619a..8bd51dc71f 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -326,13 +326,13 @@ module ActiveRecord
# class Task < ActiveRecord::Base
# end
#
- # person = Task.new(title: '', is_done: false)
- # person.attribute_present?(:title) # => false
- # person.attribute_present?(:is_done) # => true
- # person.name = 'Francesco'
- # person.is_done = true
- # person.attribute_present?(:title) # => true
- # person.attribute_present?(:is_done) # => true
+ # task = Task.new(title: '', is_done: false)
+ # task.attribute_present?(:title) # => false
+ # task.attribute_present?(:is_done) # => true
+ # task.title = 'Buy milk'
+ # task.is_done = true
+ # task.attribute_present?(:title) # => true
+ # task.attribute_present?(:is_done) # => true
def attribute_present?(attribute)
value = read_attribute(attribute)
!value.nil? && !(value.respond_to?(:empty?) && value.empty?)
@@ -359,6 +359,8 @@ module ActiveRecord
# "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
# <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
#
+ # Note: +:id+ is always present.
+ #
# Alias for the <tt>read_attribute</tt> method.
#
# class Person < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index d01e9aea59..979dfb207e 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -35,7 +35,7 @@ module ActiveRecord
extend ActiveSupport::Concern
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :time, :date]
included do
class_attribute :attribute_types_cached_by_default, instance_writer: false
@@ -52,7 +52,7 @@ module ActiveRecord
end
# Returns the attributes which are cached. By default time related columns
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
+ # with datatype <tt>:datetime, :time, :date</tt> are cached.
def cached_attributes
@cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index a1d2242f73..53a9c874bf 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -30,7 +30,8 @@ module ActiveRecord
# ==== Parameters
#
# * +attr_name+ - The field name that should be serialized.
- # * +class_name+ - Optional, class name that the object type should be equal to.
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
+ # or a class name that the object type should be equal to.
#
# ==== Example
#
@@ -38,13 +39,23 @@ module ActiveRecord
# class User < ActiveRecord::Base
# serialize :preferences
# end
- def serialize(attr_name, class_name = Object)
+ #
+ # # Serialize preferences using JSON as coder.
+ # class User < ActiveRecord::Base
+ # serialize :preferences, JSON
+ # end
+ #
+ # # Serialize preferences as Hash using YAML coder.
+ # class User < ActiveRecord::Base
+ # serialize :preferences, Hash
+ # end
+ def serialize(attr_name, class_name_or_coder = Object)
include Behavior
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
- class_name
+ coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
+ class_name_or_coder
else
- Coders::YAMLColumn.new(class_name)
+ Coders::YAMLColumn.new(class_name_or_coder)
end
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index f168282ea3..dfebb2cf56 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -28,7 +28,7 @@ module ActiveRecord
module ClassMethods
protected
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # Defined for all +datetime+ attributes when +time_zone_aware_attributes+ are enabled.
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
def define_method_attribute=(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
@@ -51,7 +51,7 @@ module ActiveRecord
def create_time_zone_conversion_attribute?(name, column)
time_zone_aware_attributes &&
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
- (:datetime == column.type || :timestamp == column.type)
+ (:datetime == column.type)
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index e9622ca0c1..1a4d2957ec 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -35,7 +35,7 @@ module ActiveRecord
#
# === One-to-one Example
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_one :author, autosave: true
# end
#
@@ -76,7 +76,7 @@ module ActiveRecord
#
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_many :comments # :autosave option is not declared
# end
#
@@ -95,20 +95,23 @@ module ActiveRecord
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
# are new records or not:
#
- # class Post
+ # class Post < ActiveRecord::Base
# has_many :comments, autosave: true
# end
#
# post = Post.create(title: 'ruby rocks')
# post.comments.create(body: 'hello world')
# post.comments[0].body = 'hi everyone'
- # post.save # => saves both post and comment, with 'hi everyone' as body
+ # post.comments.build(body: "good morning.")
+ # post.title += "!"
+ # post.save # => saves both post and comments.
#
# Destroying one of the associated models as part of the parent's save action
# is as simple as marking it for destruction:
#
- # post.comments.last.mark_for_destruction
- # post.comments.last.marked_for_destruction? # => true
+ # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
+ # post.comments[1].mark_for_destruction
+ # post.comments[1].marked_for_destruction? # => true
# post.comments.length # => 2
#
# Note that the model is _not_ yet removed from the database:
@@ -301,7 +304,8 @@ module ActiveRecord
def association_valid?(reflection, record)
return true if record.destroyed? || record.marked_for_destruction?
- unless valid = record.valid?
+ validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
+ unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
record.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
@@ -377,15 +381,16 @@ module ActiveRecord
def save_has_one_association(reflection)
association = association_instance_get(reflection.name)
record = association && association.load_target
+
if record && !record.destroyed?
autosave = reflection.options[:autosave]
if autosave && record.marked_for_destruction?
record.destroy
- else
+ elsif autosave != false
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
- if autosave != false && (autosave || new_record? || record_changed?(reflection, record, key))
+ if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
unless reflection.through_reflection
record[reflection.foreign_key] = key
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 1d47cba234..db4d5f0129 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -296,7 +296,6 @@ module ActiveRecord #:nodoc:
include Core
include Persistence
- include NoTouching
include ReadonlyAttributes
include ModelSchema
include Inheritance
@@ -318,6 +317,7 @@ module ActiveRecord #:nodoc:
include NestedAttributes
include Aggregations
include Transactions
+ include NoTouching
include Reflection
include Serialization
include Store
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 da25e640c1..bc47412405 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -9,15 +9,23 @@ module ActiveRecord
# Converts an arel AST to SQL
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
- binds = binds.dup
- visitor.accept(arel.ast) do
- quote(*binds.shift.reverse)
- end
+ collected = visitor.accept(arel.ast, collector)
+ collected.compile(binds.dup, self)
else
arel
end
end
+ # This is used in the StatementCache object. It returns an object that
+ # can be used to query the database repeatedly.
+ def cacheable_query(arel) # :nodoc:
+ if prepared_statements
+ ActiveRecord::StatementCache.query visitor, arel.ast
+ else
+ ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
+ end
+ end
+
# Returns an ActiveRecord::Result instance.
def select_all(arel, name = nil, binds = [])
arel, binds = binds_from_relation arel, binds
@@ -381,7 +389,7 @@ module ActiveRecord
end
def binds_from_relation(relation, binds)
- if relation.is_a?(Relation) && binds.blank?
+ if relation.is_a?(Relation) && binds.empty?
relation, binds = relation.arel, relation.bind_values
end
[relation, binds]
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 71c3a4378b..117c0f0969 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -99,6 +99,8 @@ module ActiveRecord
# Specifies the precision for a <tt>:decimal</tt> column.
# * <tt>:scale</tt> -
# Specifies the scale for a <tt>:decimal</tt> column.
+ # * <tt>:index</tt> -
+ # Create an index for the column. Can be either <tt>true</tt> or an options hash.
#
# For clarity's sake: the precision is the number of significant digits,
# while the scale is the number of digits that can be stored following
@@ -123,17 +125,8 @@ module ActiveRecord
# Default is (38,0).
# * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
# Default unknown.
- # * Firebird: <tt>:precision</tt> [1..18], <tt>:scale</tt> [0..18].
- # Default (9,0). Internal types NUMERIC and DECIMAL have different
- # storage rules, decimal being better.
- # * FrontBase?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
- # Default (38,0). WARNING Max <tt>:precision</tt>/<tt>:scale</tt> for
- # NUMERIC is 19, and DECIMAL is 38.
# * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
# Default (38,0).
- # * Sybase: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
- # Default (38,0).
- # * OpenBase?: Documentation unclear. Claims storage in <tt>double</tt>.
#
# This method returns <tt>self</tt>.
#
@@ -172,18 +165,21 @@ module ActiveRecord
# What can be written like this with the regular calls to column:
#
# create_table :products do |t|
- # t.column :shop_id, :integer
- # t.column :creator_id, :integer
- # t.column :name, :string, default: "Untitled"
- # t.column :value, :string, default: "Untitled"
- # t.column :created_at, :datetime
- # t.column :updated_at, :datetime
+ # t.column :shop_id, :integer
+ # t.column :creator_id, :integer
+ # t.column :item_number, :string
+ # t.column :name, :string, default: "Untitled"
+ # t.column :value, :string, default: "Untitled"
+ # t.column :created_at, :datetime
+ # t.column :updated_at, :datetime
# end
+ # add_index :products, :item_number
#
# can also be written as follows using the short-hand:
#
# create_table :products do |t|
# t.integer :shop_id, :creator_id
+ # t.string :item_number, index: true
# t.string :name, :value, default: "Untitled"
# t.timestamps
# end
@@ -219,6 +215,8 @@ module ActiveRecord
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
end
+ index_options = options.delete(:index)
+ index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
@columns_hash[name] = new_column_definition(name, type, options)
self
end
@@ -264,6 +262,7 @@ module ActiveRecord
alias :belongs_to :references
def new_column_definition(name, type, options) # :nodoc:
+ type = aliased_types[type] || type
column = create_column_definition name, type
limit = options.fetch(:limit) do
native[type][:limit] if native[type].is_a?(Hash)
@@ -294,6 +293,12 @@ module ActiveRecord
def native
@native
end
+
+ def aliased_types
+ HashWithIndifferentAccess.new(
+ timestamp: :datetime,
+ )
+ end
end
class AlterTable # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index aa99822389..ffa6af6d99 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -71,7 +71,8 @@ module ActiveRecord
# column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
#
def column_exists?(table_name, column_name, type = nil, options = {})
- columns(table_name).any?{ |c| c.name == column_name.to_s &&
+ column_name = column_name.to_s
+ columns(table_name).any?{ |c| c.name == column_name &&
(!type || c.type == type) &&
(!options.key?(:limit) || c.limit == options[:limit]) &&
(!options.key?(:precision) || c.precision == options[:precision]) &&
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ffd5055dec..0c55dbb037 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -3,9 +3,12 @@ require 'bigdecimal'
require 'bigdecimal/util'
require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache'
+require 'active_record/connection_adapters/type'
require 'active_record/connection_adapters/abstract/schema_dumper'
require 'active_record/connection_adapters/abstract/schema_creation'
require 'monitor'
+require 'arel/collectors/bind'
+require 'arel/collectors/sql_string'
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -90,6 +93,8 @@ module ActiveRecord
end
end
+ attr_reader :prepared_statements
+
def initialize(connection, logger = nil, pool = nil) #:nodoc:
super()
@@ -103,6 +108,26 @@ module ActiveRecord
@prepared_statements = false
end
+ class BindCollector < Arel::Collectors::Bind
+ def compile(bvs, conn)
+ super(bvs.map { |bv| conn.quote(*bv.reverse) })
+ end
+ end
+
+ class SQLString < Arel::Collectors::SQLString
+ def compile(bvs, conn)
+ super(bvs)
+ end
+ end
+
+ def collector
+ if prepared_statements
+ SQLString.new
+ else
+ BindCollector.new
+ end
+ end
+
def valid_type?(type)
true
end
@@ -128,16 +153,11 @@ module ActiveRecord
@owner = nil
end
- def unprepared_visitor
- self.class::BindSubstitution.new self
- end
-
def unprepared_statement
old_prepared_statements, @prepared_statements = @prepared_statements, false
- old_visitor, @visitor = @visitor, unprepared_visitor
yield
ensure
- @visitor, @prepared_statements = old_visitor, old_prepared_statements
+ @prepared_statements = old_prepared_statements
end
# Returns the human-readable name of the adapter. Use mixed case - one
@@ -318,13 +338,14 @@ module ActiveRecord
def release_savepoint(name = nil)
end
- def case_sensitive_modifier(node)
+ def case_sensitive_modifier(node, table_attribute)
node
end
def case_sensitive_comparison(table, attribute, column, value)
- value = case_sensitive_modifier(value) unless value.nil?
- table[attribute].eq(value)
+ table_attr = table[attribute]
+ value = case_sensitive_modifier(value, table_attr) unless value.nil?
+ table_attr.eq(value)
end
def case_insensitive_comparison(table, attribute, column, value)
@@ -340,8 +361,48 @@ module ActiveRecord
pool.checkin self
end
+ def type_map # :nodoc:
+ @type_map ||= Type::TypeMap.new.tap do |mapping|
+ initialize_type_map(mapping)
+ end
+ end
+
protected
+ def lookup_cast_type(sql_type) # :nodoc:
+ type_map.lookup(sql_type)
+ end
+
+ def initialize_type_map(m) # :nodoc:
+ m.register_type %r(boolean)i, Type::Boolean.new
+ m.register_type %r(char)i, Type::String.new
+ m.register_type %r(binary)i, Type::Binary.new
+ m.alias_type %r(blob)i, 'binary'
+ m.register_type %r(text)i, Type::Text.new
+ m.alias_type %r(clob)i, 'text'
+ m.register_type %r(date)i, Type::Date.new
+ m.register_type %r(time)i, Type::Time.new
+ m.alias_type %r(timestamp)i, 'datetime'
+ m.register_type %r(datetime)i, Type::DateTime.new
+ m.alias_type %r(numeric)i, 'decimal'
+ m.alias_type %r(number)i, 'decimal'
+ m.register_type %r(float)i, Type::Float.new
+ m.alias_type %r(double)i, 'float'
+ m.register_type %r(int)i, Type::Integer.new
+ m.register_type(%r(decimal)i) do |sql_type|
+ if Type.extract_scale(sql_type) == 0
+ Type::Integer.new
+ else
+ Type::Decimal.new
+ end
+ end
+ end
+
+ def reload_type_map # :nodoc:
+ type_map.clear
+ initialize_type_map(type_map)
+ end
+
def translate_exception_class(e, sql)
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger
@@ -368,7 +429,13 @@ module ActiveRecord
end
def without_prepared_statement?(binds)
- !@prepared_statements || binds.empty?
+ !prepared_statements || binds.empty?
+ end
+
+ def column_for(table_name, column_name) # :nodoc:
+ column_name = column_name.to_s
+ columns(table_name).detect { |c| c.name == column_name } ||
+ raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}")
end
end
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
index 20eea208ec..86eb2a38d8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -56,11 +56,11 @@ module ActiveRecord
class Column < ConnectionAdapters::Column # :nodoc:
attr_reader :collation, :strict, :extra
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
+ def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
@strict = strict
@collation = collation
@extra = extra
- super(name, default, sql_type, null)
+ super(name, default, cast_type, sql_type, null)
end
def extract_default(default)
@@ -97,18 +97,6 @@ module ActiveRecord
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 /^enum\((.+)\)/i
@@ -129,6 +117,8 @@ module ActiveRecord
when /^mediumint/i; 3
when /^smallint/i; 2
when /^tinyint/i; 1
+ when /^float/i; 24
+ when /^double/i; 53
else
super
end
@@ -173,7 +163,6 @@ module ActiveRecord
:float => { :name => "float" },
:decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "blob" },
@@ -183,21 +172,18 @@ module ActiveRecord
INDEX_TYPES = [:fulltext, :spatial]
INDEX_USINGS = [:btree, :hash]
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
# 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 = {}, {}
+ @visitor = Arel::Visitors::MySQL.new self
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
- @visitor = Arel::Visitors::MySQL.new self
else
- @visitor = unprepared_visitor
+ @prepared_statements = false
end
end
@@ -263,9 +249,9 @@ module ActiveRecord
raise NotImplementedError
end
- # Overridden by the adapters to instantiate their specific Column type.
- def new_column(field, default, type, null, collation, extra = "") # :nodoc:
- Column.new(field, default, type, null, collation, extra)
+ def new_column(field, default, sql_type, null, collation, extra = "") # :nodoc:
+ cast_type = lookup_cast_type(sql_type)
+ Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
end
# Must return the Mysql error number from the exception, if the exception has an
@@ -318,6 +304,11 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
+ def clear_cache!
+ super
+ reload_type_map
+ end
+
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
log(sql, name) { @connection.query(sql) }
@@ -610,7 +601,8 @@ module ActiveRecord
pk_and_sequence && pk_and_sequence.first
end
- def case_sensitive_modifier(node)
+ def case_sensitive_modifier(node, table_attribute)
+ node = Arel::Nodes.build_quoted node, table_attribute
Arel::Nodes::Bin.new(node)
end
@@ -644,6 +636,15 @@ module ActiveRecord
protected
+ def initialize_type_map(m)
+ super
+ m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
+ m.alias_type %r(enum)i, 'varchar'
+ m.alias_type %r(set)i, 'varchar'
+ m.alias_type %r(year)i, 'integer'
+ m.alias_type %r(bit)i, 'binary'
+ end
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
# to give it some prompting in the form of a subsubquery. Ugh!
def subquery_for(key, select)
@@ -713,15 +714,13 @@ module ActiveRecord
end
def rename_column_sql(table_name, column_name, new_column_name)
- options = { name: new_column_name }
-
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
- options[:default] = column.default
- options[:null] = column.null
- options[:auto_increment] = (column.extra == "auto_increment")
- else
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
- end
+ column = column_for(table_name, column_name)
+ options = {
+ name: new_column_name,
+ default: column.default,
+ null: column.null,
+ auto_increment: column.extra == "auto_increment"
+ }
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
schema_creation.accept ChangeColumnDefinition.new column, current_type, options
@@ -759,30 +758,23 @@ module ActiveRecord
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
-
def configure_connection
- variables = @config[:variables] || {}
+ variables = @config.fetch(:variables, {}).stringify_keys
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
- variables[:sql_auto_is_null] = 0
+ variables['sql_auto_is_null'] = 0
# Increase timeout so the server doesn't disconnect us.
wait_timeout = @config[:wait_timeout]
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
- variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout)
+ variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
# If the user has provided another value for sql_mode, don't replace it.
- if strict_mode? && !variables.has_key?(:sql_mode)
- variables[:sql_mode] = 'STRICT_ALL_TABLES'
+ if strict_mode? && !variables.has_key?('sql_mode')
+ variables['sql_mode'] = 'STRICT_ALL_TABLES'
end
# NAMES does not have an equals sign, see
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 187eefb9e4..a718756b93 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -13,104 +13,46 @@ module ActiveRecord
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
end
- attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function
+ attr_reader :name, :default, :cast_type, :limit, :null, :sql_type, :precision, :scale, :default_function
attr_accessor :primary, :coder
alias :encoded? :coder
+ delegate :type, :klass, :text?, :number?, :binary?, :type_cast_for_write, to: :cast_type
+
# Instantiates a new column in the table.
#
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
+ # +cast_type+ is the object used for type casting and type information.
# +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
# <tt>company_name varchar(60)</tt>.
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type = nil, null = true)
+ def initialize(name, default, cast_type, sql_type = nil, null = true)
@name = name
+ @cast_type = cast_type
@sql_type = sql_type
@null = null
@limit = extract_limit(sql_type)
@precision = extract_precision(sql_type)
@scale = extract_scale(sql_type)
- @type = simplified_type(sql_type)
@default = extract_default(default)
@default_function = nil
@primary = nil
@coder = nil
end
- # Returns +true+ if the column is either of type string or text.
- def text?
- type == :string || type == :text
- end
-
- # Returns +true+ if the column is either of type integer, float or decimal.
- def number?
- type == :integer || type == :float || type == :decimal
- end
-
def has_default?
!default.nil?
end
- # Returns the Ruby class that corresponds to the abstract data type.
- def klass
- case type
- when :integer then Fixnum
- when :float then Float
- when :decimal then BigDecimal
- when :datetime, :timestamp, :time then Time
- when :date then Date
- when :text, :string, :binary then String
- when :boolean then Object
- end
- end
-
- def binary?
- type == :binary
- end
-
- # Casts a Ruby value to something appropriate for writing to the database.
- def type_cast_for_write(value)
- return value unless number?
-
- case value
- when FalseClass
- 0
- when TrueClass
- 1
- when String
- value.presence
- else
- value
- end
- end
-
# Casts value to an appropriate instance.
def type_cast(value)
- return nil if value.nil?
- return coder.load(value) if encoded?
-
- klass = self.class
-
- case type
- when :string, :text
- case value
- when TrueClass; "1"
- when FalseClass; "0"
- else
- value.to_s
- end
- when :integer then klass.value_to_integer(value)
- when :float then value.to_f
- when :decimal then klass.value_to_decimal(value)
- when :datetime, :timestamp then klass.string_to_time(value)
- when :time then klass.string_to_dummy_time(value)
- when :date then klass.value_to_date(value)
- when :binary then klass.binary_to_string(value)
- when :boolean then klass.value_to_boolean(value)
- else value
+ if encoded?
+ coder.load(value)
+ else
+ cast_type.type_cast(value)
end
end
@@ -126,132 +68,9 @@ module ActiveRecord
type_cast(default)
end
- class << self
- # Used to convert from BLOBs to Strings
- def binary_to_string(value)
- value
- end
-
- def value_to_date(value)
- if value.is_a?(String)
- return nil if value.empty?
- fast_string_to_date(value) || fallback_string_to_date(value)
- elsif value.respond_to?(:to_date)
- value.to_date
- else
- value
- end
- end
-
- def string_to_time(string)
- return string unless string.is_a?(String)
- return nil if string.empty?
-
- fast_string_to_time(string) || fallback_string_to_time(string)
- end
-
- def string_to_dummy_time(string)
- return string unless string.is_a?(String)
- return nil if string.empty?
-
- dummy_time_string = "2000-01-01 #{string}"
-
- fast_string_to_time(dummy_time_string) || begin
- time_hash = Date._parse(dummy_time_string)
- return nil if time_hash[:hour].nil?
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
- end
- end
-
- # convert something to a boolean
- def value_to_boolean(value)
- if value.is_a?(String) && value.empty?
- nil
- else
- TRUE_VALUES.include?(value)
- end
- end
-
- # Used to convert values to integer.
- # handle the case when an integer column is used to store boolean values
- def value_to_integer(value)
- case value
- when TrueClass, FalseClass
- value ? 1 : 0
- else
- value.to_i rescue nil
- end
- end
-
- # convert something to a BigDecimal
- def value_to_decimal(value)
- # Using .class is faster than .is_a? and
- # subclasses of BigDecimal will be handled
- # in the else clause
- if value.class == BigDecimal
- value
- elsif value.respond_to?(:to_d)
- value.to_d
- else
- value.to_s.to_d
- end
- end
-
- protected
- # '0.123456' -> 123456
- # '1.123456' -> 123456
- def microseconds(time)
- time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
- end
-
- def new_date(year, mon, mday)
- if year && year != 0
- Date.new(year, mon, mday) rescue nil
- end
- end
-
- def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
- # Treat 0000-00-00 00:00:00 as nil.
- return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
-
- if offset
- time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
- return nil unless time
-
- time -= offset
- Base.default_timezone == :utc ? time : time.getlocal
- else
- Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
- end
- end
-
- def fast_string_to_date(string)
- if string =~ Format::ISO_DATE
- new_date $1.to_i, $2.to_i, $3.to_i
- end
- end
-
- # Doesn't handle time zones.
- def fast_string_to_time(string)
- if string =~ Format::ISO_DATETIME
- microsec = ($7.to_r * 1_000_000).to_i
- new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
- end
- end
-
- def fallback_string_to_date(string)
- new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
- end
-
- def fallback_string_to_time(string)
- time_hash = Date._parse(string)
- time_hash[:sec_fraction] = microseconds(time_hash)
-
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
- end
- end
-
private
+ delegate :extract_scale, to: :cast_type
+
def extract_limit(sql_type)
$1.to_i if sql_type =~ /\((.*)\)/
end
@@ -259,40 +78,6 @@ module ActiveRecord
def extract_precision(sql_type)
$2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
end
-
- def extract_scale(sql_type)
- case sql_type
- when /^(numeric|decimal|number)\((\d+)\)/i then 0
- when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
- end
- end
-
- def simplified_type(field_type)
- case field_type
- when /int/i
- :integer
- when /float|double/i
- :float
- when /decimal|numeric|number/i
- extract_scale(field_type) == 0 ? :integer : :decimal
- when /datetime/i
- :datetime
- when /timestamp/i
- :timestamp
- when /time/i
- :time
- when /date/i
- :date
- when /clob/i, /text/i
- :text
- when /blob/i, /binary/i
- :binary
- when /char/i
- :string
- when /boolean/i
- :boolean
- end
- end
end
end
# :startdoc:
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index e0715f7ce9..b79d1a4458 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -33,7 +33,7 @@ module ActiveRecord
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
@uri = URI.parse(url)
- @adapter = @uri.scheme
+ @adapter = @uri.scheme.gsub('-', '_')
@adapter = "postgresql" if @adapter == "postgres"
if @uri.opaque
@@ -62,8 +62,8 @@ module ActiveRecord
# Converts the query parameters of the URI into a hash.
#
- # "localhost?pool=5&reap_frequency=2"
- # # => { "pool" => "5", "reap_frequency" => "2" }
+ # "localhost?pool=5&reaping_frequency=2"
+ # # => { "pool" => "5", "reaping_frequency" => "2" }
#
# returns empty hash if no query present.
#
@@ -220,10 +220,10 @@ module ActiveRecord
# an environment key or a URL spec, so we have deprecated
# this ambiguous behaviour and in the future this function
# can be removed in favor of resolve_url_connection.
- if configurations.key?(spec)
+ if configurations.key?(spec) || spec !~ /:/
ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \
"for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
- resolve_connection(configurations[spec])
+ resolve_symbol_connection(spec)
else
resolve_url_connection(spec)
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 5e82fdcbe0..0a14cdfe89 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -29,18 +29,11 @@ module ActiveRecord
module ConnectionAdapters
class Mysql2Adapter < AbstractMysqlAdapter
-
- class Column < AbstractMysqlAdapter::Column # :nodoc:
- def adapter
- Mysql2Adapter
- end
- end
-
ADAPTER_NAME = 'Mysql2'
def initialize(connection, logger, connection_options, config)
super
- @visitor = BindSubstitution.new self
+ @prepared_statements = false
configure_connection
end
@@ -69,10 +62,6 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation, extra = "") # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?, extra)
- end
-
def error_number(exception)
exception.error_number if exception.respond_to?(:error_number)
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index e6aa2ba921..8beb869ce2 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -66,35 +66,6 @@ module ActiveRecord
# * <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,
- value.month,
- value.day,
- value.hour,
- value.minute,
- value.second,
- value.second_part)
- end
-
- 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 self.string_to_date(v)
- return super unless Mysql::Time === v
- new_date(v.year, v.month, v.day)
- end
-
- def adapter
- MysqlAdapter
- end
- end
-
ADAPTER_NAME = 'MySQL'
class StatementPool < ConnectionAdapters::StatementPool
@@ -156,10 +127,6 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation, extra = "") # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?, extra)
- end
-
def error_number(exception) # :nodoc:
exception.errno if exception.respond_to?(:errno)
end
@@ -222,6 +189,7 @@ module ActiveRecord
# Clears the prepared statements cache.
def clear_cache!
+ super
@statements.clear
end
@@ -295,125 +263,69 @@ module ActiveRecord
end
module Fields
- class Type
- def type; end
-
- def type_cast_for_write(value)
- value
- end
- end
-
- class Identity < Type
- def type_cast(value); value; end
- end
-
- class Integer < Type
- def type_cast(value)
- return if value.nil?
-
- value.to_i rescue value ? 1 : 0
- end
- end
-
- class Date < Type
- def type; :date; end
-
- def type_cast(value)
- return if value.nil?
-
- # FIXME: probably we can improve this since we know it is mysql
- # specific
- ConnectionAdapters::Column.value_to_date value
- end
- end
-
- class DateTime < Type
- def type; :datetime; end
-
- def type_cast(value)
- return if value.nil?
-
- # FIXME: probably we can improve this since we know it is mysql
- # specific
- ConnectionAdapters::Column.string_to_time value
- end
- end
-
- class Time < Type
- def type; :time; end
-
- def type_cast(value)
- return if value.nil?
-
- # FIXME: probably we can improve this since we know it is mysql
- # specific
- ConnectionAdapters::Column.string_to_dummy_time value
- end
- end
-
- class Float < Type
- def type; :float; end
-
- def type_cast(value)
- return if value.nil?
-
- value.to_f
+ class DateTime < Type::DateTime
+ def cast_value(value)
+ if Mysql::Time === value
+ new_time(
+ value.year,
+ value.month,
+ value.day,
+ value.hour,
+ value.minute,
+ value.second,
+ value.second_part)
+ else
+ super
+ end
end
end
- class Decimal < Type
- def type_cast(value)
- return if value.nil?
-
- ConnectionAdapters::Column.value_to_decimal value
+ class Time < Type::Time
+ def cast_value(value)
+ if Mysql::Time === value
+ new_time(
+ 2000,
+ 01,
+ 01,
+ value.hour,
+ value.minute,
+ value.second,
+ value.second_part)
+ else
+ super
+ end
end
end
- class Boolean < Type
- def type_cast(value)
- return if value.nil?
+ class << self
+ TYPES = ConnectionAdapters::Type::HashLookupTypeMap.new # :nodoc:
- ConnectionAdapters::Column.value_to_boolean value
- end
- end
-
- TYPES = {}
-
- # Register an MySQL +type_id+ with a typecasting object in
- # +type+.
- def self.register_type(type_id, type)
- TYPES[type_id] = type
- end
+ delegate :register_type, :alias_type, to: :TYPES
- def self.alias_type(new, old)
- TYPES[new] = TYPES[old]
- end
-
- def self.find_type(field)
- if field.type == Mysql::Field::TYPE_TINY && field.length > 1
- TYPES[Mysql::Field::TYPE_LONG]
- else
- TYPES.fetch(field.type) { Fields::Identity.new }
+ def find_type(field)
+ if field.type == Mysql::Field::TYPE_TINY && field.length > 1
+ TYPES.lookup(Mysql::Field::TYPE_LONG)
+ else
+ TYPES.lookup(field.type)
+ end
end
end
- register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
- register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
+ register_type Mysql::Field::TYPE_TINY, Type::Boolean.new
+ register_type Mysql::Field::TYPE_LONG, Type::Integer.new
alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
- register_type Mysql::Field::TYPE_VAR_STRING, Fields::Identity.new
- register_type Mysql::Field::TYPE_BLOB, Fields::Identity.new
- register_type Mysql::Field::TYPE_DATE, Fields::Date.new
+ register_type Mysql::Field::TYPE_DATE, Type::Date.new
register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
register_type Mysql::Field::TYPE_TIME, Fields::Time.new
- register_type Mysql::Field::TYPE_FLOAT, Fields::Float.new
+ register_type Mysql::Field::TYPE_FLOAT, Type::Float.new
+ end
- Mysql::Field.constants.grep(/TYPE/).map { |class_name|
- Mysql::Field.const_get class_name
- }.reject { |const| TYPES.key? const }.each do |const|
- register_type const, Fields::Identity.new
- end
+ def initialize_type_map(m) # :nodoc:
+ super
+ m.register_type %r(datetime)i, Fields::DateTime.new
+ m.register_type %r(time)i, Fields::Time.new
end
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
@@ -431,7 +343,7 @@ module ActiveRecord
fields << field_name
if field.decimals > 0
- types[field_name] = Fields::Decimal.new
+ types[field_name] = Type::Decimal.new
else
types[field_name] = Fields.find_type field
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
index 0b218f2bfd..1b74c039ce 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLColumn < Column
- module ArrayParser
+ module PostgreSQL
+ module ArrayParser # :nodoc:
DOUBLE_QUOTE = '"'
BACKSLASH = "\\"
@@ -9,35 +9,23 @@ module ActiveRecord
BRACKET_OPEN = '{'
BRACKET_CLOSE = '}'
- private
- # Loads pg_array_parser if available. String parsing can be
- # performed quicker by a native extension, which will not create
- # a large amount of Ruby objects that will need to be garbage
- # collected. pg_array_parser has a C and Java extension
- begin
- require 'pg_array_parser'
- include PgArrayParser
- rescue LoadError
- def parse_pg_array(string)
- parse_data(string)
+ def parse_pg_array(string) # :nodoc:
+ local_index = 0
+ array = []
+ while(local_index < string.length)
+ case string[local_index]
+ when BRACKET_OPEN
+ local_index,array = parse_array_contents(array, string, local_index + 1)
+ when BRACKET_CLOSE
+ return array
end
+ local_index += 1
end
- def parse_data(string)
- local_index = 0
- array = []
- while(local_index < string.length)
- case string[local_index]
- when BRACKET_OPEN
- local_index,array = parse_array_contents(array, string, local_index + 1)
- when BRACKET_CLOSE
- return array
- end
- local_index += 1
- end
+ array
+ end
- array
- end
+ private
def parse_array_contents(array, string, index)
is_escaping = false
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 551a9289c3..0cbedb0987 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -1,32 +1,19 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLColumn < Column
- module Cast
- def point_to_string(point)
+ module PostgreSQL
+ module Cast # :nodoc:
+ def point_to_string(point) # :nodoc:
"(#{point[0]},#{point[1]})"
end
- def string_to_point(string)
+ def string_to_point(string) # :nodoc:
if string[0] == '(' && string[-1] == ')'
string = string[1...-1]
end
string.split(',').map{ |v| Float(v) }
end
- def string_to_time(string)
- return string unless String === string
-
- case string
- when 'infinity'; Float::INFINITY
- when '-infinity'; -Float::INFINITY
- when / BC$/
- super("-" + string.sub(/ BC$/, ""))
- else
- super
- end
- end
-
- def string_to_bit(value)
+ def string_to_bit(value) # :nodoc:
case value
when /^0x/i
value[2..-1].hex.to_s(2) # Hexadecimal notation
@@ -35,7 +22,7 @@ module ActiveRecord
end
end
- def hstore_to_string(object, array_member = false)
+ def hstore_to_string(object, array_member = false) # :nodoc:
if Hash === object
string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(',')
string = escape_hstore(string) if array_member
@@ -45,7 +32,7 @@ module ActiveRecord
end
end
- def string_to_hstore(string)
+ def string_to_hstore(string) # :nodoc:
if string.nil?
nil
elsif String === string
@@ -59,7 +46,7 @@ module ActiveRecord
end
end
- def json_to_string(object)
+ def json_to_string(object) # :nodoc:
if Hash === object || Array === object
ActiveSupport::JSON.encode(object)
else
@@ -67,7 +54,7 @@ module ActiveRecord
end
end
- def array_to_string(value, column, adapter)
+ def array_to_string(value, column, adapter) # :nodoc:
casted_values = value.map do |val|
if String === val
if val == "NULL"
@@ -82,13 +69,13 @@ module ActiveRecord
"{#{casted_values.join(',')}}"
end
- def range_to_string(object)
+ def range_to_string(object) # :nodoc:
from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin
to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end
"[#{from},#{to}#{object.exclude_end? ? ')' : ']'}"
end
- def string_to_json(string)
+ def string_to_json(string) # :nodoc:
if String === string
ActiveSupport::JSON.decode(string)
else
@@ -96,7 +83,7 @@ module ActiveRecord
end
end
- def string_to_cidr(string)
+ def string_to_cidr(string) # :nodoc:
if string.nil?
nil
elsif String === string
@@ -110,7 +97,7 @@ module ActiveRecord
end
end
- def cidr_to_string(object)
+ def cidr_to_string(object) # :nodoc:
if IPAddr === object
"#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
else
@@ -118,7 +105,7 @@ module ActiveRecord
end
end
- def string_to_array(string, oid)
+ def string_to_array(string, oid) # :nodoc:
parse_pg_array(string).map {|val| type_cast_array(oid, val)}
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index 2cbcd5fd50..95f52312a5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -1,3 +1,5 @@
+require 'active_record/connection_adapters/postgresql/cast'
+
module ActiveRecord
module ConnectionAdapters
# PostgreSQL-specific extensions to column definitions in a table.
@@ -10,27 +12,31 @@ module ActiveRecord
if sql_type =~ /\[\]$/
@array = true
- super(name, default_value, sql_type[0..sql_type.length - 3], null)
+ super(name, default_value, oid_type, sql_type[0..sql_type.length - 3], null)
else
@array = false
- super(name, default_value, sql_type, null)
+ super(name, default_value, oid_type, sql_type, null)
end
@default_function = default if has_default_function?(default_value, default)
end
- def number?
- !array && super
- end
-
- def text?
- !array && super
- end
-
# :stopdoc:
class << self
- include ConnectionAdapters::PostgreSQLColumn::Cast
- include ConnectionAdapters::PostgreSQLColumn::ArrayParser
+ include PostgreSQL::Cast
+
+ # Loads pg_array_parser if available. String parsing can be
+ # performed quicker by a native extension, which will not create
+ # a large amount of Ruby objects that will need to be garbage
+ # collected. pg_array_parser has a C and Java extension
+ begin
+ require 'pg_array_parser'
+ include PgArrayParser
+ rescue LoadError
+ require 'active_record/connection_adapters/postgresql/array_parser'
+ include PostgreSQL::ArrayParser
+ end
+
attr_accessor :money_precision
end
# :startdoc:
@@ -98,21 +104,6 @@ module ActiveRecord
end
end
- def type_cast_for_write(value)
- if @oid_type.respond_to?(:type_cast_for_write)
- @oid_type.type_cast_for_write(value)
- else
- super
- end
- end
-
- def type_cast(value)
- return if value.nil?
- return super if encoded?
-
- @oid_type.type_cast value
- end
-
def accessor
@oid_type.accessor
end
@@ -132,12 +123,6 @@ module ActiveRecord
end
end
- # Extracts the scale from PostgreSQL-specific data types.
- def extract_scale(sql_type)
- # Money type has a fixed scale of 2.
- sql_type =~ /^money/ ? 2 : super
- end
-
# Extracts the precision from PostgreSQL-specific data types.
def extract_precision(sql_type)
if sql_type == 'money'
@@ -148,11 +133,6 @@ module ActiveRecord
super
end
end
-
- # Maps PostgreSQL-specific data types to logical Rails types.
- def simplified_type(field_type)
- @oid_type.simplified_type(field_type) || super
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 51ee2829b2..89a7257d77 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
+ module PostgreSQL
module DatabaseStatements
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel, binds)}"
@@ -44,10 +44,32 @@ module ActiveRecord
end
end
+ def select_value(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
+ end
+ end
+
+ def select_values(arel, name = nil)
+ arel, binds = binds_from_relation arel, []
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ if result.nfields > 0
+ result.column_values(0)
+ else
+ []
+ end
+ end
+ end
+
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
+ execute_and_clear(sql, name, binds) do |result|
+ result.values
+ end
end
# Executes an INSERT query and returns the new record's ID
@@ -72,6 +94,11 @@ module ActiveRecord
super.insert
end
+ # The internal PostgreSQL identifier of the money data type.
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
+ # The internal PostgreSQL identifier of the BYTEA data type.
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
+
# create a 2D array representing the result set
def result_as_array(res) #:nodoc:
# check if we have any binary column and if they need escaping
@@ -134,31 +161,20 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
- exec_cache(sql, name, binds)
-
- types = {}
- fields = result.fields
- fields.each_with_index do |fname, i|
- ftype = result.ftype i
- fmod = result.fmod i
- types[fname] = type_map.fetch(ftype, fmod) { |oid, mod|
- warn "unknown OID: #{fname}(#{oid}) (#{sql})"
- OID::Identity.new
- }
+ execute_and_clear(sql, name, binds) do |result|
+ types = {}
+ fields = result.fields
+ fields.each_with_index do |fname, i|
+ ftype = result.ftype i
+ fmod = result.fmod i
+ types[fname] = get_oid_type(ftype, fmod, fname)
+ end
+ ActiveRecord::Result.new(fields, result.values, types)
end
-
- ret = ActiveRecord::Result.new(fields, result.values, types)
- result.clear
- return ret
end
def exec_delete(sql, name = 'SQL', binds = [])
- result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
- exec_cache(sql, name, binds)
- affected = result.cmd_tuples
- result.clear
- affected
+ execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
end
alias :exec_update :exec_delete
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 9e898015a6..1d0384c3df 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,49 +1,26 @@
-require 'active_record/connection_adapters/abstract_adapter'
-
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
- module OID
- class Type
- def type; end
- def simplified_type(sql_type); type end
-
+ module PostgreSQL
+ module OID # :nodoc:
+ module Infinity
def infinity(options = {})
- ::Float::INFINITY * (options[:negative] ? -1 : 1)
- end
- end
-
- class Identity < Type
- def type_cast(value)
- value
- end
- end
-
- class String < Type
- def type; :string end
-
- def type_cast(value)
- return if value.nil?
-
- value.to_s
+ options[:negative] ? -::Float::INFINITY : ::Float::INFINITY
end
end
- class SpecializedString < OID::String
- def type; @type end
+ class SpecializedString < Type::String
+ attr_reader :type
def initialize(type)
@type = type
end
- end
- class Text < OID::String
- def type; :text end
+ def text?
+ false
+ end
end
- class Bit < Type
- def type; :string end
-
+ class Bit < Type::String
def type_cast(value)
if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_bit value
@@ -53,20 +30,20 @@ module ActiveRecord
end
end
- class Bytea < Type
- def type; :binary end
-
- def type_cast(value)
- return if value.nil?
+ class Bytea < Type::Binary
+ def cast_value(value)
PGconn.unescape_bytea value
end
end
- class Money < Type
- def type; :decimal end
+ class Money < Type::Decimal
+ include Infinity
- def type_cast(value)
- return if value.nil?
+ def extract_scale(sql_type)
+ 2
+ end
+
+ def cast_value(value)
return value unless ::String === value
# Because money output is formatted according to the locale, there are two
@@ -85,11 +62,11 @@ module ActiveRecord
value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
end
- ConnectionAdapters::Column.value_to_decimal value
+ super(value)
end
end
- class Vector < Type
+ class Vector < Type::Value
attr_reader :delim, :subtype
# +delim+ corresponds to the `typdelim` column in the pg_types
@@ -108,9 +85,7 @@ module ActiveRecord
end
end
- class Point < Type
- def type; :string end
-
+ class Point < Type::String
def type_cast(value)
if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_point value
@@ -120,10 +95,10 @@ module ActiveRecord
end
end
- class Array < Type
- def type; @subtype.type end
-
+ class Array < Type::Value
attr_reader :subtype
+ delegate :type, to: :subtype
+
def initialize(subtype)
@subtype = subtype
end
@@ -137,12 +112,12 @@ module ActiveRecord
end
end
- class Range < Type
- attr_reader :subtype
- def simplified_type(sql_type); sql_type.to_sym end
+ class Range < Type::Value
+ attr_reader :subtype, :type
- def initialize(subtype)
+ def initialize(subtype, type)
@subtype = subtype
+ @type = type
end
def extract_bounds(value)
@@ -163,8 +138,8 @@ module ActiveRecord
infinity?(value) ? value : @subtype.type_cast(value)
end
- def type_cast(value)
- return if value.nil? || value == 'empty'
+ def cast_value(value)
+ return if value == 'empty'
return value if value.is_a?(::Range)
extracted = extract_bounds(value)
@@ -186,112 +161,77 @@ This is not reliable and will be removed in the future.
end
end
- class Integer < Type
- def type; :integer end
-
- def type_cast(value)
- return if value.nil?
-
- ConnectionAdapters::Column.value_to_integer value
- end
+ class Integer < Type::Integer
+ include Infinity
end
- class Boolean < Type
- def type; :boolean end
+ class DateTime < Type::DateTime
+ include Infinity
- def type_cast(value)
- return if value.nil?
-
- ConnectionAdapters::Column.value_to_boolean value
- end
- end
-
- class Timestamp < Type
- def type; :timestamp; end
- def simplified_type(sql_type)
- case sql_type
- when /^timestamp with(?:out)? time zone$/
- :datetime
+ def cast_value(value)
+ if value.is_a?(::String)
+ case value
+ when 'infinity' then ::Float::INFINITY
+ when '-infinity' then -::Float::INFINITY
+ when / BC$/
+ super("-" + value.sub(/ BC$/, ""))
+ else
+ super
+ end
else
- :timestamp
+ value
end
end
-
- def type_cast(value)
- return if value.nil?
-
- # FIXME: probably we can improve this since we know it is PG
- # specific
- ConnectionAdapters::PostgreSQLColumn.string_to_time value
- end
end
- class Date < Type
- def type; :date; end
-
- def type_cast(value)
- return if value.nil?
-
- # FIXME: probably we can improve this since we know it is PG
- # specific
- ConnectionAdapters::Column.value_to_date value
- end
+ class Date < Type::Date
+ include Infinity
end
- class Time < Type
- def type; :time end
-
- def type_cast(value)
- return if value.nil?
-
- # FIXME: probably we can improve this since we know it is PG
- # specific
- ConnectionAdapters::Column.string_to_dummy_time value
- end
+ class Time < Type::Time
+ include Infinity
end
- class Float < Type
- def type; :float end
+ class Float < Type::Float
+ include Infinity
def type_cast(value)
- return if value.nil?
-
- value.to_f
+ case value
+ when nil then nil
+ when 'Infinity' then ::Float::INFINITY
+ when '-Infinity' then -::Float::INFINITY
+ when 'NaN' then ::Float::NAN
+ else value.to_f
+ end
end
end
- class Decimal < Type
- def type; :decimal end
-
- def type_cast(value)
- return if value.nil?
-
- ConnectionAdapters::Column.value_to_decimal value
- end
-
+ class Decimal < Type::Decimal
def infinity(options = {})
BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
end
end
- class Enum < Type
- def type; :enum end
+ class Enum < Type::Value
+ def type
+ :enum
+ end
def type_cast(value)
value.to_s
end
end
- class Hstore < Type
- def type; :hstore end
+ class Hstore < Type::Value
+ def type
+ :hstore
+ end
def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.hstore_to_string value
end
- def type_cast(value)
- return if value.nil?
-
+ def cast_value(value)
ConnectionAdapters::PostgreSQLColumn.string_to_hstore value
end
@@ -300,28 +240,32 @@ This is not reliable and will be removed in the future.
end
end
- class Cidr < Type
- def type; :cidr end
- def type_cast(value)
- return if value.nil?
+ class Cidr < Type::Value
+ def type
+ :cidr
+ end
+ def cast_value(value)
ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
end
end
+
class Inet < Cidr
- def type; :inet end
+ def type
+ :inet
+ end
end
- class Json < Type
- def type; :json end
+ class Json < Type::Value
+ def type
+ :json
+ end
def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.json_to_string value
end
- def type_cast(value)
- return if value.nil?
-
+ def cast_value(value)
ConnectionAdapters::PostgreSQLColumn.string_to_json value
end
@@ -330,47 +274,84 @@ This is not reliable and will be removed in the future.
end
end
- class Uuid < Type
- def type; :uuid end
+ class Uuid < Type::Value
+ def type
+ :uuid
+ end
+
def type_cast(value)
value.presence
end
end
- class TypeMap
- def initialize
- @mapping = {}
+ # This class uses the data from PostgreSQL pg_type table to build
+ # the OID -> Type mapping.
+ # - OID is and integer representing the type.
+ # - Type is an OID::Type object.
+ # This class has side effects on the +store+ passed during initialization.
+ class TypeMapInitializer # :nodoc:
+ def initialize(store)
+ @store = store
end
- def []=(oid, type)
- @mapping[oid] = type
+ def run(records)
+ mapped, nodes = records.partition { |row| OID.registered_type? row['typname'] }
+ ranges, nodes = nodes.partition { |row| row['typtype'] == 'r' }
+ enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
+ domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
+ composites, nodes = nodes.partition { |row| row['typelem'] != '0' }
+
+ mapped.each { |row| register_mapped_type(row) }
+ enums.each { |row| register_enum_type(row) }
+ domains.each { |row| register_domain_type(row) }
+ arrays.each { |row| register_array_type(row) }
+ ranges.each { |row| register_range_type(row) }
+ composites.each { |row| register_composite_type(row) }
end
- def [](oid)
- @mapping[oid]
+ private
+ def register_mapped_type(row)
+ register row['oid'], OID::NAMES[row['typname']]
end
- def clear
- @mapping.clear
+ def register_enum_type(row)
+ register row['oid'], OID::Enum.new
end
- def key?(oid)
- @mapping.key? oid
+ def register_array_type(row)
+ if subtype = @store.lookup(row['typelem'].to_i)
+ register row['oid'], OID::Array.new(subtype)
+ end
end
- def fetch(ftype, fmod)
- # The type for the numeric depends on the width of the field,
- # so we'll do something special here.
- #
- # When dealing with decimal columns:
- #
- # places after decimal = fmod - 4 & 0xffff
- # places before decimal = (fmod - 4) >> 16 & 0xffff
- if ftype == 1700 && (fmod - 4 & 0xffff).zero?
- ftype = 23
+ def register_range_type(row)
+ if subtype = @store.lookup(row['rngsubtype'].to_i)
+ register row['oid'], OID::Range.new(subtype, row['typname'].to_sym)
end
+ end
+
+ def register_domain_type(row)
+ if base_type = @store.lookup(row["typbasetype"].to_i)
+ register row['oid'], base_type
+ else
+ warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}."
+ end
+ end
+
+ def register_composite_type(row)
+ if subtype = @store.lookup(row['typelem'].to_i)
+ register row['oid'], OID::Vector.new(row['typdelim'], subtype)
+ end
+ end
+
+ def register(oid, oid_type)
+ oid = oid.to_i
+
+ raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
+ return if @store.key?(oid)
- @mapping.fetch(ftype) { |oid| yield oid, fmod }
+ @store.register_type(oid, oid_type)
end
end
@@ -379,7 +360,7 @@ This is not reliable and will be removed in the future.
# type_map is then dynamically built with oids as the key and type
# objects as values.
NAMES = Hash.new { |h,k| # :nodoc:
- h[k] = OID::Identity.new
+ h[k] = Type::Value.new
}
# Register an OID type named +name+ with a typecasting object in
@@ -406,14 +387,15 @@ This is not reliable and will be removed in the future.
register_type 'numeric', OID::Decimal.new
register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'
- register_type 'text', OID::Text.new
- register_type 'varchar', OID::String.new
+ register_type 'text', Type::Text.new
+ register_type 'varchar', Type::String.new
alias_type 'char', 'varchar'
+ alias_type 'name', 'varchar'
alias_type 'bpchar', 'varchar'
- register_type 'bool', OID::Boolean.new
+ register_type 'bool', Type::Boolean.new
register_type 'bit', OID::Bit.new
alias_type 'varbit', 'bit'
- register_type 'timestamp', OID::Timestamp.new
+ register_type 'timestamp', OID::DateTime.new
alias_type 'timestamptz', 'timestamp'
register_type 'date', OID::Date.new
register_type 'time', OID::Time.new
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 210172cf32..ad12298013 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -1,17 +1,17 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
+ module PostgreSQL
module Quoting
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
- PGconn.escape_bytea(value) if value
+ @connection.escape_bytea(value) if value
end
# Unescapes bytea output from a database to the binary string it represents.
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
# on escaped binary output from database drive.
def unescape_bytea(value)
- PGconn.unescape_bytea(value) if value
+ @connection.unescape_bytea(value) if value
end
# Quotes PostgreSQL-specific data types for SQL input.
@@ -150,13 +150,11 @@ module ActiveRecord
# - "schema.name".table_name
# - "schema.name"."table.name"
def quote_table_name(name)
- schema, name_part = extract_pg_identifier_from_name(name.to_s)
-
- unless name_part
- quote_column_name(schema)
+ schema, table = Utils.extract_schema_and_table(name.to_s)
+ if schema
+ "#{quote_column_name(schema)}.#{quote_column_name(table)}"
else
- table_name, name_part = extract_pg_identifier_from_name(name_part)
- "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
+ quote_column_name(table)
end
end
@@ -182,6 +180,15 @@ module ActiveRecord
end
result
end
+
+ # Does not quote function default values for UUID columns
+ def quote_default_value(value, column) #:nodoc:
+ if column.type == :uuid && value =~ /\(\)/
+ value
+ else
+ quote(value, column)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index bc775394a6..52b307c432 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -1,12 +1,12 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
- module ReferentialIntegrity
- def supports_disable_referential_integrity? #:nodoc:
+ module PostgreSQL
+ module ReferentialIntegrity # :nodoc:
+ def supports_disable_referential_integrity? # :nodoc:
true
end
- def disable_referential_integrity #:nodoc:
+ def disable_referential_integrity # :nodoc:
if supports_disable_referential_integrity?
begin
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 50a73aa666..539ba38c4a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
+ module PostgreSQL
class SchemaCreation < AbstractAdapter::SchemaCreation
private
@@ -12,7 +12,7 @@ module ActiveRecord
def visit_ColumnDefinition(o)
sql = super
- if o.primary_key? && o.type == :uuid
+ if o.primary_key? && o.type != :primary_key
sql << " PRIMARY KEY "
add_column_options!(sql, column_options(o))
end
@@ -33,10 +33,6 @@ module ActiveRecord
end
end
- def schema_creation
- SchemaCreation.new self
- end
-
module SchemaStatements
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
@@ -182,9 +178,7 @@ module ActiveRecord
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
- oid = type_map.fetch(oid.to_i, fmod.to_i) {
- OID::Identity.new
- }
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name)
PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
end
end
@@ -406,13 +400,16 @@ module ActiveRecord
# Changes the default value of a table column.
def change_column_default(table_name, column_name, default)
clear_cache!
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
+ column = column_for(table_name, column_name)
+
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column
end
def change_column_null(table_name, column_name, null, default = nil)
clear_cache!
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")
+ column = column_for(table_name, column_name)
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
end
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
new file mode 100644
index 0000000000..60ffd3a114
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -0,0 +1,25 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module Utils # :nodoc:
+ extend self
+
+ # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
+ # +schema_name+ is nil if not specified in +name+.
+ # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
+ # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
+ #
+ # * <tt>table_name</tt>
+ # * <tt>"table.name"</tt>
+ # * <tt>schema_name.table_name</tt>
+ # * <tt>schema_name."table.name"</tt>
+ # * <tt>"schema_name".table_name</tt>
+ # * <tt>"schema.name"."table name"</tt>
+ def extract_schema_and_table(name)
+ table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
+ [schema, table]
+ end
+ 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 3510e4f3b0..ed3e884455 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,13 +1,14 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
+
+require 'active_record/connection_adapters/postgresql/utils'
+require 'active_record/connection_adapters/postgresql/column'
require 'active_record/connection_adapters/postgresql/oid'
-require 'active_record/connection_adapters/postgresql/cast'
-require 'active_record/connection_adapters/postgresql/array_parser'
require 'active_record/connection_adapters/postgresql/quoting'
+require 'active_record/connection_adapters/postgresql/referential_integrity'
require 'active_record/connection_adapters/postgresql/schema_statements'
require 'active_record/connection_adapters/postgresql/database_statements'
-require 'active_record/connection_adapters/postgresql/referential_integrity'
-require 'active_record/connection_adapters/postgresql/column'
+
require 'arel/visitors/bind_visitor'
# Make sure we're using pg high enough for PGResult#values
@@ -215,7 +216,6 @@ module ActiveRecord
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "timestamp" },
- timestamp: { name: "timestamp" },
time: { name: "time" },
date: { name: "date" },
daterange: { name: "daterange" },
@@ -238,10 +238,12 @@ module ActiveRecord
citext: { name: "citext" }
}
- include Quoting
- include ReferentialIntegrity
- include SchemaStatements
- include DatabaseStatements
+ OID = PostgreSQL::OID #:nodoc:
+
+ include PostgreSQL::Quoting
+ include PostgreSQL::ReferentialIntegrity
+ include PostgreSQL::SchemaStatements
+ include PostgreSQL::DatabaseStatements
include Savepoints
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -249,6 +251,10 @@ module ActiveRecord
ADAPTER_NAME
end
+ def schema_creation
+ PostgreSQL::SchemaCreation.new self
+ end
+
# Adds `:array` option to the default set provided by the
# AbstractAdapter
def prepare_column_options(column, types)
@@ -338,19 +344,15 @@ module ActiveRecord
end
end
- class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
+ @visitor = Arel::Visitors::PostgreSQL.new self
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
- @visitor = Arel::Visitors::PostgreSQL.new self
else
- @visitor = unprepared_visitor
+ @prepared_statements = false
end
@connection_parameters, @config = connection_parameters, config
@@ -367,7 +369,7 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
- @type_map = OID::TypeMap.new
+ @type_map = Type::HashLookupTypeMap.new
initialize_type_map(type_map)
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
@@ -498,25 +500,6 @@ module ActiveRecord
exec_query "SET SESSION AUTHORIZATION #{user}"
end
- module Utils
- extend self
-
- # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
- # +schema_name+ is nil if not specified in +name+.
- # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
- # +name+ supports the range of schema/table references understood by PostgreSQL, for example:
- #
- # * <tt>table_name</tt>
- # * <tt>"table.name"</tt>
- # * <tt>schema_name.table_name</tt>
- # * <tt>schema_name."table.name"</tt>
- # * <tt>"schema.name"."table name"</tt>
- def extract_schema_and_table(name)
- table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
- [schema, table]
- end
- end
-
def use_insert_returning?
@use_insert_returning
end
@@ -555,96 +538,67 @@ module ActiveRecord
private
- def type_map
- @type_map
- end
+ def get_oid_type(oid, fmod, column_name)
+ if !type_map.key?(oid)
+ initialize_type_map(type_map, [oid])
+ end
- def reload_type_map
- type_map.clear
- initialize_type_map(type_map)
+ type_map.fetch(normalize_oid_type(oid, fmod)) {
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
+ Type::Value.new.tap do |cast_type|
+ type_map.register_type(oid, cast_type)
+ end
+ }
end
- def add_oid(row, records_by_oid, type_map)
- return type_map if type_map.key? row['type_elem'].to_i
-
- if OID.registered_type? row['typname']
- # this composite type is explicitly registered
- vector = OID::NAMES[row['typname']]
+ def normalize_oid_type(ftype, fmod)
+ # The type for the numeric depends on the width of the field,
+ # so we'll do something special here.
+ #
+ # When dealing with decimal columns:
+ #
+ # places after decimal = fmod - 4 & 0xffff
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
+ if ftype == 1700 && (fmod - 4 & 0xffff).zero?
+ 23
else
- # use the default for composite types
- unless type_map.key? row['typelem'].to_i
- add_oid records_by_oid[row['typelem']], records_by_oid, type_map
- end
-
- vector = OID::Vector.new row['typdelim'], type_map[row['typelem'].to_i]
+ ftype
end
-
- type_map[row['oid'].to_i] = vector
- type_map
end
- def initialize_type_map(type_map)
+ def initialize_type_map(type_map, oids = nil)
if supports_ranges?
- result = execute(<<-SQL, 'SCHEMA')
+ query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
SQL
else
- result = execute(<<-SQL, 'SCHEMA')
+ query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
FROM pg_type as t
SQL
end
- ranges, nodes = result.partition { |row| row['typtype'] == 'r' }
- enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
- domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
- arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
- leaves, nodes = nodes.partition { |row| row['typelem'] == '0' }
-
- # populate the enum types
- enums.each do |row|
- type_map[row['oid'].to_i] = OID::Enum.new
- end
-
- # populate the base types
- leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
- type_map[row['oid'].to_i] = OID::NAMES[row['typname']]
- end
-
- records_by_oid = result.group_by { |row| row['oid'] }
-
- # populate composite types
- nodes.each do |row|
- add_oid row, records_by_oid, type_map
- end
- # populate array types
- arrays.find_all { |row| type_map.key? row['typelem'].to_i }.each do |row|
- array = OID::Array.new type_map[row['typelem'].to_i]
- type_map[row['oid'].to_i] = array
+ if oids
+ query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
end
- # populate range types
- ranges.find_all { |row| type_map.key? row['rngsubtype'].to_i }.each do |row|
- subtype = type_map[row['rngsubtype'].to_i]
- range = OID::Range.new subtype
- type_map[row['oid'].to_i] = range
- end
-
- # populate domain types
- domains.each do |row|
- base_type_oid = row["typbasetype"].to_i
- if base_type = type_map[base_type_oid]
- type_map[row['oid'].to_i] = base_type
- else
- warn "unknown base type (OID: #{base_type_oid}) for domain #{row["typname"]}."
- end
- end
+ initializer = OID::TypeMapInitializer.new(type_map)
+ records = execute(query, 'SCHEMA')
+ initializer.run(records)
end
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
+ def execute_and_clear(sql, name, binds)
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
+ exec_cache(sql, name, binds)
+ ret = yield result
+ result.clear
+ ret
+ end
+
def exec_no_cache(sql, name, binds)
log(sql, name, binds) { @connection.async_exec(sql) }
end
@@ -704,11 +658,6 @@ module ActiveRecord
@statements[sql_key]
end
- # The internal PostgreSQL identifier of the money data type.
- MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
- # The internal PostgreSQL identifier of the BYTEA data type.
- BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
-
# Connects to a PostgreSQL server and sets up the adapter depending on the
# connected server's characteristics.
def connect
@@ -799,7 +748,7 @@ module ActiveRecord
# Query implementation notes:
# - format_type includes the column size constraint, e.g. varchar(50)
# - ::regclass is a function that gives the id for a table name
- def column_definitions(table_name) #:nodoc:
+ def column_definitions(table_name) # :nodoc:
exec_query(<<-end_sql, 'SCHEMA').rows
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
@@ -811,7 +760,7 @@ module ActiveRecord
end_sql
end
- def extract_pg_identifier_from_name(name)
+ def extract_pg_identifier_from_name(name) # :nodoc:
match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
if match_data
@@ -821,12 +770,12 @@ module ActiveRecord
end
end
- def extract_table_ref_from_insert_sql(sql)
+ def extract_table_ref_from_insert_sql(sql) # :nodoc:
sql[/into\s+([^\(]*).*values\s*\(/im]
$1.strip if $1
end
- def create_table_definition(name, temporary, options, as = nil)
+ def create_table_definition(name, temporary, options, as = nil) # :nodoc:
TableDefinition.new native_database_types, name, temporary, options, as
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index cd1f7a16c6..a5e2619cb8 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -30,7 +30,7 @@ module ActiveRecord
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
- ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
raise ActiveRecord::NoDatabaseError.new(error.message, error)
@@ -41,14 +41,12 @@ module ActiveRecord
end
module ConnectionAdapters #:nodoc:
- class SQLite3Column < Column #:nodoc:
- class << self
- def binary_to_string(value)
- if value.encoding != Encoding::ASCII_8BIT
- value = value.force_encoding(Encoding::ASCII_8BIT)
- end
- value
+ class SQLite3Binary < Type::Binary # :nodoc:
+ def cast_value(value)
+ if value.encoding != Encoding::ASCII_8BIT
+ value = value.force_encoding(Encoding::ASCII_8BIT)
end
+ value
end
end
@@ -69,7 +67,6 @@ module ActiveRecord
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "datetime" },
- timestamp: { name: "datetime" },
time: { name: "time" },
date: { name: "date" },
binary: { name: "blob" },
@@ -123,11 +120,7 @@ module ActiveRecord
end
end
- class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
- def initialize(connection, logger, config)
+ def initialize(connection, logger, connection_options, config)
super(connection, logger)
@active = nil
@@ -135,11 +128,12 @@ module ActiveRecord
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@config = config
+ @visitor = Arel::Visitors::SQLite.new self
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
- @visitor = Arel::Visitors::SQLite.new self
else
- @visitor = unprepared_visitor
+ @prepared_statements = false
end
end
@@ -273,7 +267,7 @@ module ActiveRecord
def explain(arel, binds = [])
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
end
class ExplainPrettyPrinter
@@ -299,9 +293,12 @@ module ActiveRecord
# Don't cache statements if they are not prepared
if without_prepared_statement?(binds)
stmt = @connection.prepare(sql)
- cols = stmt.columns
- records = stmt.to_a
- stmt.close
+ begin
+ cols = stmt.columns
+ records = stmt.to_a
+ ensure
+ stmt.close
+ end
stmt = records
else
cache = @statements[sql] ||= {
@@ -394,7 +391,9 @@ module ActiveRecord
field["dflt_value"] = $1.gsub('""', '"')
end
- SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
+ sql_type = field['type']
+ cast_type = lookup_cast_type(sql_type)
+ Column.new(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
end
end
@@ -495,14 +494,18 @@ module ActiveRecord
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
- unless columns(table_name).detect{|c| c.name == column_name.to_s }
- raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
- end
- alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
- rename_column_indexes(table_name, column_name, new_column_name)
+ column = column_for(table_name, column_name)
+ alter_table(table_name, rename: {column.name => new_column_name.to_s})
+ rename_column_indexes(table_name, column.name, new_column_name)
end
protected
+
+ def initialize_type_map(m)
+ super
+ m.register_type(/binary/i, SQLite3Binary.new)
+ end
+
def select(sql, name = nil, binds = []) #:nodoc:
exec_query(sql, name, binds)
end
diff --git a/activerecord/lib/active_record/connection_adapters/type.rb b/activerecord/lib/active_record/connection_adapters/type.rb
new file mode 100644
index 0000000000..763176cb2b
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type.rb
@@ -0,0 +1,32 @@
+require 'active_record/connection_adapters/type/numeric'
+require 'active_record/connection_adapters/type/time_value'
+require 'active_record/connection_adapters/type/value'
+
+require 'active_record/connection_adapters/type/binary'
+require 'active_record/connection_adapters/type/boolean'
+require 'active_record/connection_adapters/type/date'
+require 'active_record/connection_adapters/type/date_time'
+require 'active_record/connection_adapters/type/decimal'
+require 'active_record/connection_adapters/type/float'
+require 'active_record/connection_adapters/type/integer'
+require 'active_record/connection_adapters/type/string'
+require 'active_record/connection_adapters/type/text'
+require 'active_record/connection_adapters/type/time'
+
+require 'active_record/connection_adapters/type/type_map'
+require 'active_record/connection_adapters/type/hash_lookup_type_map'
+
+module ActiveRecord
+ module ConnectionAdapters
+ module Type # :nodoc:
+ class << self
+ def extract_scale(sql_type)
+ case sql_type
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/binary.rb b/activerecord/lib/active_record/connection_adapters/type/binary.rb
new file mode 100644
index 0000000000..4b2d1a66e0
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/binary.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Binary < Value # :nodoc:
+ def type
+ :binary
+ end
+
+ def binary?
+ true
+ end
+
+ def klass
+ ::String
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/boolean.rb b/activerecord/lib/active_record/connection_adapters/type/boolean.rb
new file mode 100644
index 0000000000..2337bdd563
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/boolean.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Boolean < Value # :nodoc:
+ def type
+ :boolean
+ end
+
+ private
+
+ def cast_value(value)
+ if value == ''
+ nil
+ else
+ Column::TRUE_VALUES.include?(value)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/date.rb b/activerecord/lib/active_record/connection_adapters/type/date.rb
new file mode 100644
index 0000000000..1e7205fd0b
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/date.rb
@@ -0,0 +1,44 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Date < Value # :nodoc:
+ def type
+ :date
+ end
+
+ def klass
+ ::Date
+ end
+
+ private
+
+ def cast_value(value)
+ if value.is_a?(::String)
+ return if value.empty?
+ fast_string_to_date(value) || fallback_string_to_date(value)
+ elsif value.respond_to?(:to_date)
+ value.to_date
+ else
+ value
+ end
+ end
+
+ def fast_string_to_date(string)
+ if string =~ Column::Format::ISO_DATE
+ new_date $1.to_i, $2.to_i, $3.to_i
+ end
+ end
+
+ def fallback_string_to_date(string)
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
+ end
+
+ def new_date(year, mon, mday)
+ if year && year != 0
+ ::Date.new(year, mon, mday) rescue nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/date_time.rb b/activerecord/lib/active_record/connection_adapters/type/date_time.rb
new file mode 100644
index 0000000000..c34f4c5a53
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/date_time.rb
@@ -0,0 +1,35 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class DateTime < Value # :nodoc:
+ include TimeValue
+
+ def type
+ :datetime
+ end
+
+ private
+
+ def cast_value(string)
+ return string unless string.is_a?(::String)
+ return if string.empty?
+
+ fast_string_to_time(string) || fallback_string_to_time(string)
+ end
+
+ # '0.123456' -> 123456
+ # '1.123456' -> 123456
+ def microseconds(time)
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
+ end
+
+ def fallback_string_to_time(string)
+ time_hash = ::Date._parse(string)
+ time_hash[:sec_fraction] = microseconds(time_hash)
+
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/decimal.rb b/activerecord/lib/active_record/connection_adapters/type/decimal.rb
new file mode 100644
index 0000000000..ac5af4b963
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/decimal.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Decimal < Value # :nodoc:
+ include Numeric
+
+ def type
+ :decimal
+ end
+
+ def klass
+ ::BigDecimal
+ end
+
+ private
+
+ def cast_value(value)
+ if value.respond_to?(:to_d)
+ value.to_d
+ else
+ value.to_s.to_d
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/float.rb b/activerecord/lib/active_record/connection_adapters/type/float.rb
new file mode 100644
index 0000000000..51cfa5d86a
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/float.rb
@@ -0,0 +1,23 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Float < Value # :nodoc:
+ include Numeric
+
+ def type
+ :float
+ end
+
+ def klass
+ ::Float
+ end
+
+ private
+
+ def cast_value(value)
+ value.to_f
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb
new file mode 100644
index 0000000000..8503d3ea1b
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/hash_lookup_type_map.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class HashLookupTypeMap < TypeMap # :nodoc:
+ delegate :key?, to: :@mapping
+
+ def lookup(type)
+ @mapping.fetch(type, proc { default_value }).call(type)
+ end
+
+ def fetch(type, &block)
+ @mapping.fetch(type, block).call(type)
+ end
+
+ def alias_type(type, alias_type)
+ register_type(type) { lookup(alias_type) }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/integer.rb b/activerecord/lib/active_record/connection_adapters/type/integer.rb
new file mode 100644
index 0000000000..8f3469434c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/integer.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Integer < Value # :nodoc:
+ include Numeric
+
+ def type
+ :integer
+ end
+
+ def klass
+ ::Fixnum
+ end
+
+ private
+
+ def cast_value(value)
+ case value
+ when true then 1
+ when false then 0
+ else value.to_i rescue nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/numeric.rb b/activerecord/lib/active_record/connection_adapters/type/numeric.rb
new file mode 100644
index 0000000000..a3379831cb
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/numeric.rb
@@ -0,0 +1,20 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ module Numeric # :nodoc:
+ def number?
+ true
+ end
+
+ def type_cast_for_write(value)
+ case value
+ when true then 1
+ when false then 0
+ when ::String then value.presence
+ else super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/string.rb b/activerecord/lib/active_record/connection_adapters/type/string.rb
new file mode 100644
index 0000000000..55f0e1ee1c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/string.rb
@@ -0,0 +1,29 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class String < Value # :nodoc:
+ def type
+ :string
+ end
+
+ def text?
+ true
+ end
+
+ def klass
+ ::String
+ end
+
+ private
+
+ def cast_value(value)
+ case value
+ when true then "1"
+ when false then "0"
+ else value.to_s
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/text.rb b/activerecord/lib/active_record/connection_adapters/type/text.rb
new file mode 100644
index 0000000000..ee5842a3fc
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/text.rb
@@ -0,0 +1,13 @@
+require 'active_record/connection_adapters/type/string'
+
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Text < String # :nodoc:
+ def type
+ :text
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/time.rb b/activerecord/lib/active_record/connection_adapters/type/time.rb
new file mode 100644
index 0000000000..4dd201e3fe
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/time.rb
@@ -0,0 +1,28 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Time < Value # :nodoc:
+ include TimeValue
+
+ def type
+ :time
+ end
+
+ private
+
+ def cast_value(value)
+ return value unless value.is_a?(::String)
+ return if value.empty?
+
+ dummy_time_value = "2000-01-01 #{value}"
+
+ fast_string_to_time(dummy_time_value) || begin
+ time_hash = ::Date._parse(dummy_time_value)
+ return if time_hash[:hour].nil?
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/time_value.rb b/activerecord/lib/active_record/connection_adapters/type/time_value.rb
new file mode 100644
index 0000000000..e9ca4adeda
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/time_value.rb
@@ -0,0 +1,36 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ module TimeValue # :nodoc:
+ def klass
+ ::Time
+ end
+
+ private
+
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
+ # Treat 0000-00-00 00:00:00 as nil.
+ return if year.nil? || (year == 0 && mon == 0 && mday == 0)
+
+ if offset
+ time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
+ return unless time
+
+ time -= offset
+ Base.default_timezone == :utc ? time : time.getlocal
+ else
+ ::Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ end
+ end
+
+ # Doesn't handle time zones.
+ def fast_string_to_time(string)
+ if string =~ Column::Format::ISO_DATETIME
+ microsec = ($7.to_r * 1_000_000).to_i
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/type_map.rb b/activerecord/lib/active_record/connection_adapters/type/type_map.rb
new file mode 100644
index 0000000000..d89171a820
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/type_map.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class TypeMap # :nodoc:
+ def initialize
+ @mapping = {}
+ end
+
+ def lookup(lookup_key)
+ matching_pair = @mapping.reverse_each.detect do |key, _|
+ key === lookup_key
+ end
+
+ if matching_pair
+ matching_pair.last.call(lookup_key)
+ else
+ default_value
+ end
+ end
+
+ def register_type(key, value = nil, &block)
+ raise ::ArgumentError unless value || block
+
+ if block
+ @mapping[key] = block
+ else
+ @mapping[key] = proc { value }
+ end
+ end
+
+ def alias_type(key, target_key)
+ register_type(key) do |sql_type|
+ metadata = sql_type[/\(.*\)/, 0]
+ lookup("#{target_key}#{metadata}")
+ end
+ end
+
+ def clear
+ @mapping.clear
+ end
+
+ private
+
+ def default_value
+ @default_value ||= Value.new
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/type/value.rb b/activerecord/lib/active_record/connection_adapters/type/value.rb
new file mode 100644
index 0000000000..289c27f6d4
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/type/value.rb
@@ -0,0 +1,43 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class Value # :nodoc:
+ def type; end
+
+ def extract_scale(sql_type)
+ Type.extract_scale(sql_type)
+ end
+
+ def type_cast(value)
+ cast_value(value) unless value.nil?
+ end
+
+ def type_cast_for_write(value)
+ value
+ end
+
+ def text?
+ false
+ end
+
+ def number?
+ false
+ end
+
+ def binary?
+ false
+ end
+
+ def klass
+ ::Object
+ end
+
+ private
+
+ def cast_value(value)
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index bbb866cedf..31e7390bf7 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -58,9 +58,9 @@ module ActiveRecord
end
class MergeAndResolveDefaultUrlConfig # :nodoc:
- def initialize(raw_configurations, url = ENV['DATABASE_URL'])
+ def initialize(raw_configurations)
@raw_config = raw_configurations.dup
- @url = url
+ @env = DEFAULT_ENV.call.to_s
end
# Returns fully resolved connection hashes.
@@ -71,33 +71,10 @@ module ActiveRecord
private
def config
- if @url
- raw_merged_into_default
- else
- @raw_config
- end
- end
-
- def raw_merged_into_default
- default = default_url_hash
-
- @raw_config.each do |env, values|
- default[env] = values || {}
- default[env].merge!("url" => @url) { |h, v1, v2| v1 || v2 } if default[env].is_a?(Hash)
- end
- default
- end
-
- # When the raw configuration is not present and ENV['DATABASE_URL']
- # is available we return a hash with the connection information in
- # the connection URL. This hash responds to any string key with
- # resolved connection information.
- def default_url_hash
- Hash.new do |hash, key|
- hash[key] = if key.is_a? String
- ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash
- else
- nil
+ @raw_config.dup.tap do |cfg|
+ if url = ENV['DATABASE_URL']
+ cfg[@env] ||= {}
+ cfg[@env]["url"] ||= url
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 4e53f66005..4571cc0786 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -94,6 +94,7 @@ module ActiveRecord
end
class_attribute :default_connection_handler, instance_writer: false
+ class_attribute :find_by_statement_cache
def self.connection_handler
ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
@@ -107,6 +108,71 @@ module ActiveRecord
end
module ClassMethods
+ def initialize_find_by_cache
+ self.find_by_statement_cache = {}.extend(Mutex_m)
+ end
+
+ def inherited(child_class)
+ child_class.initialize_find_by_cache
+ super
+ end
+
+ def find(*ids)
+ # We don't have cache keys for this stuff yet
+ return super unless ids.length == 1
+ return super if block_given? ||
+ primary_key.nil? ||
+ default_scopes.any? ||
+ columns_hash.include?(inheritance_column) ||
+ ids.first.kind_of?(Array)
+
+ id = ids.first
+ if ActiveRecord::Base === id
+ id = id.id
+ ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \
+ "Please pass the id of the object by calling `.id`"
+ end
+ key = primary_key
+
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
+ find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
+ where(key => params.bind).limit(1)
+ }
+ }
+ record = s.execute([id], self, connection).first
+ unless record
+ raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
+ end
+ record
+ end
+
+ def find_by(*args)
+ return super if current_scope || args.length > 1 || reflect_on_all_aggregations.any?
+
+ hash = args.first
+
+ return super if hash.values.any? { |v|
+ v.nil? || Array === v || Hash === v
+ }
+
+ key = hash.keys
+
+ klass = self
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
+ find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
+ wheres = key.each_with_object({}) { |param,o|
+ o[param] = params.bind
+ }
+ klass.where(wheres).limit(1)
+ }
+ }
+ begin
+ s.execute(hash.values, self, connection).first
+ rescue TypeError => e
+ raise ActiveRecord::StatementInvalid.new(e.message, e)
+ end
+ end
+
def initialize_generated_modules
super
@@ -267,6 +333,7 @@ module ActiveRecord
@attributes_cache = {}
@new_record = true
+ @destroyed = false
super
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index dcbdf75627..71e176a328 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# ==== Parameters
#
# * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more association counters to reset
+ # * +counters+ - One or more association counters to reset. Association name or counter name can be given.
#
# ==== Examples
#
@@ -19,9 +19,14 @@ module ActiveRecord
# Post.reset_counters(1, :comments)
def reset_counters(id, *counters)
object = find(id)
- counters.each do |association|
- has_many_association = reflect_on_association(association.to_sym)
- raise ArgumentError, "'#{self.name}' has no association called '#{association}'" unless has_many_association
+ counters.each do |counter_association|
+ has_many_association = reflect_on_association(counter_association.to_sym)
+ unless has_many_association
+ has_many = reflect_on_all_associations(:has_many)
+ has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym }
+ counter_association = has_many_association.plural_name if has_many_association
+ end
+ raise ArgumentError, "'#{self.name}' has no association called '#{counter_association}'" unless has_many_association
if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
has_many_association = has_many_association.through_reflection
@@ -34,7 +39,7 @@ module ActiveRecord
counter_name = reflection.counter_cache_column
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
- arel_table[counter_name] => object.send(association).count
+ arel_table[counter_name] => object.send(counter_association).count
}, primary_key)
connection.update stmt
end
@@ -118,5 +123,54 @@ module ActiveRecord
update_counters(id, counter_name => -1)
end
end
+
+ protected
+
+ def actually_destroyed?
+ @_actually_destroyed
+ end
+
+ def clear_destroy_state
+ @_actually_destroyed = nil
+ end
+
+ private
+
+ def _create_record(*)
+ id = super
+
+ each_counter_cached_associations do |association|
+ if send(association.reflection.name)
+ association.increment_counters
+ @_after_create_counter_called = true
+ end
+ end
+
+ id
+ end
+
+ def destroy_row
+ affected_rows = super
+
+ if affected_rows > 0
+ each_counter_cached_associations do |association|
+ foreign_key = association.reflection.foreign_key.to_sym
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
+ if send(association.reflection.name)
+ association.decrement_counters
+ end
+ end
+ end
+ end
+
+ affected_rows
+ end
+
+ def each_counter_cached_associations
+ reflections.each do |name, reflection|
+ yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
+ end
+ end
+
end
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 4aa323fb00..c7ec093824 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/deep_dup'
+
module ActiveRecord
# Declare an enum attribute where the values map to integers in the database,
# but can be queried by name. Example:
@@ -25,8 +27,10 @@ module ActiveRecord
# conversation.status # => nil
#
# Scopes based on the allowed values of the enum field will be provided
- # as well. With the above example, it will create an +active+ and +archived+
- # scope.
+ # as well. With the above example:
+ #
+ # Conversation.active
+ # Conversation.archived
#
# You can set the default value from the database declaration, like:
#
@@ -65,10 +69,14 @@ module ActiveRecord
#
# Where conditions on an enum attribute must use the ordinal value of an enum.
module Enum
- DEFINED_ENUMS = {} # :nodoc:
+ def self.extended(base)
+ base.class_attribute(:defined_enums)
+ base.defined_enums = {}
+ end
- def enum_mapping_for(attr_name) # :nodoc:
- DEFINED_ENUMS[attr_name.to_s]
+ def inherited(base)
+ base.defined_enums = defined_enums.deep_dup
+ super
end
def enum(definitions)
@@ -122,9 +130,8 @@ module ActiveRecord
klass.send(:detect_enum_conflict!, name, value, true)
klass.scope value, -> { klass.where name => i }
end
-
- DEFINED_ENUMS[name.to_s] = enum_values
end
+ defined_enums[name.to_s] = enum_values
end
end
@@ -134,7 +141,7 @@ module ActiveRecord
mod = Module.new do
private
def save_changed_attribute(attr_name, value)
- if (mapping = self.class.enum_mapping_for(attr_name))
+ if (mapping = self.class.defined_enums[attr_name.to_s])
if attribute_changed?(attr_name)
old = changed_attributes[attr_name]
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6f134bbef8..47d32fae05 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -2,6 +2,7 @@ require 'erb'
require 'yaml'
require 'zlib'
require 'active_support/dependencies'
+require 'active_support/core_ext/securerandom'
require 'active_record/fixture_set/file'
require 'active_record/errors'
@@ -550,9 +551,13 @@ module ActiveRecord
end
# Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^30.
- def self.identify(label)
- Zlib.crc32(label.to_s) % MAX_ID
+ # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
+ def self.identify(label, column_type = :integer)
+ if column_type == :uuid
+ SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, label.to_s)
+ else
+ Zlib.crc32(label.to_s) % MAX_ID
+ end
end
# Superclass for the evaluation contexts used by ERB fixtures.
@@ -633,7 +638,7 @@ module ActiveRecord
# generate a primary key if necessary
if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = ActiveRecord::FixtureSet.identify(label)
+ row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
end
# If STI is used, find the correct subclass for association reflection
@@ -656,7 +661,8 @@ module ActiveRecord
row[association.foreign_type] = $1
end
- row[fk_name] = ActiveRecord::FixtureSet.identify(value)
+ fk_type = association.active_record.columns_hash[association.foreign_key].type
+ row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
end
when :has_many
if association.options[:through]
@@ -683,6 +689,10 @@ module ActiveRecord
def name
@association.name
end
+
+ def primary_key_type
+ @association.klass.column_types[@association.klass.primary_key].type
+ end
end
class HasManyThroughProxy < ReflectionProxy # :nodoc:
@@ -700,17 +710,22 @@ module ActiveRecord
@primary_key_name ||= model_class && model_class.primary_key
end
+ def primary_key_type
+ @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type
+ end
+
def add_join_records(rows, row, association)
# This is the case when the join table has no fixtures file
if (targets = row.delete(association.name.to_s))
- table_name = association.join_table
- lhs_key = association.lhs_key
- rhs_key = association.rhs_key
+ table_name = association.join_table
+ column_type = association.primary_key_type
+ lhs_key = association.lhs_key
+ rhs_key = association.rhs_key
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
rows[table_name].concat targets.map { |target|
{ lhs_key => row[primary_key_name],
- rhs_key => ActiveRecord::FixtureSet.identify(target) }
+ rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
}
end
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 654ef21b07..eb64d197f0 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -25,7 +25,7 @@ module ActiveRecord
if column.binary?
# This specifically deals with the PG adapter that casts bytea columns into a Hash.
value = value[:value] if value.is_a?(Hash)
- value = "<#{value.bytesize} bytes of binary data>"
+ value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
end
[column.name, value]
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index b6b02322d7..8fe32bcb6c 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -195,7 +195,7 @@ module ActiveRecord
# == Database support
#
# Migrations are currently supported in MySQL, PostgreSQL, SQLite,
- # SQL Server, Sybase, and Oracle (all supported databases except DB2).
+ # SQL Server, and Oracle (all supported databases except DB2).
#
# == More examples
#
@@ -640,7 +640,7 @@ module ActiveRecord
say_with_time "#{method}(#{arg_list})" do
unless @connection.respond_to? :revert
- unless arguments.empty? || method == :execute
+ unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
arguments[0] = proper_table_name(arguments.first, table_name_options)
arguments[1] = proper_table_name(arguments.second, table_name_options) if method == :rename_table
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index dc5ff02882..aa1166750f 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -29,6 +29,10 @@ module ActiveRecord
# :singleton-method:
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
# "people_basecamp"). By default, the suffix is the empty string.
+ #
+ # If you are organising your models within modules, you can add a suffix to the models within
+ # a namespace by defining a singleton method in the parent module called table_name_suffix which
+ # returns your chosen suffix.
class_attribute :table_name_suffix, instance_writer: false
self.table_name_suffix = ""
@@ -153,6 +157,10 @@ module ActiveRecord
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
+ def full_table_name_suffix #:nodoc:
+ (parents.detect {|p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
+ end
+
# Defines the name of the table column which will store the class name on single-table
# inheritance situations.
#
@@ -190,7 +198,7 @@ module ActiveRecord
# given block. This is required for Oracle and is useful for any
# database which relies on sequences for primary key generation.
#
- # If a sequence name is not explicitly set when using Oracle or Firebird,
+ # If a sequence name is not explicitly set when using Oracle,
# it will default to the commonly used pattern of: #{table_name}_seq
#
# If a sequence name is not explicitly set when using PostgreSQL, it
@@ -337,7 +345,8 @@ module ActiveRecord
contained = contained.singularize if parent.pluralize_table_names
contained += '_'
end
- "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
+
+ "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
else
# STI subclasses always use their superclass' table.
base.table_name
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 9d92e747d4..29ed499b1b 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -485,10 +485,10 @@ module ActiveRecord
end
# Takes in a limit and checks if the attributes_collection has too many
- # records. The method will take limits in the form of symbols, procs, and
- # number-like objects (anything that can be compared with an integer).
+ # records. It accepts limit in the form of symbol, proc, or
+ # number-like object (anything that can be compared with an integer).
#
- # Will raise an TooManyRecords error if the attributes_collection is
+ # Raises TooManyRecords error if the attributes_collection is
# larger than the limit.
def check_record_limit!(limit, attributes_collection)
if limit
@@ -516,10 +516,10 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
+ ConnectionAdapters::Type::Boolean.new.type_cast(hash['_destroy'])
end
- # Determines if a new record should be build by checking for
+ # Determines if a new record should be rejected by checking
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 5b255c3fe5..05d0c41678 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
def count(*)
- 0
+ calculate :count, nil
end
def sum(*)
@@ -54,7 +54,7 @@ module ActiveRecord
# TODO: Remove _options argument as soon we remove support to
# activerecord-deprecated_finders.
if operation == :count
- 0
+ group_values.any? ? Hash.new : 0
else
nil
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 13d7432773..b74e340b3e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
# Given an attributes hash, +instantiate+ returns a new instance of
- # the appropriate class.
+ # the appropriate class. Accepts only keys as strings.
#
# For example, +Post.all+ may return Comments, Messages, and Emails
# by storing the record's subclass in a +type+ attribute. By calling
@@ -46,10 +46,10 @@ module ActiveRecord
#
# See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
# how this "single-table" inheritance mapping is implemented.
- def instantiate(record, column_types = {})
- klass = discriminate_class_for_record(record)
+ def instantiate(attributes, column_types = {})
+ klass = discriminate_class_for_record(attributes)
column_types = klass.decorate_columns(column_types.dup)
- klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
+ klass.allocate.init_with('attributes' => attributes, 'column_types' => column_types)
end
private
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 6b0459ea37..9e8e5fe94b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -83,8 +83,7 @@ db_namespace = namespace :db do
desc 'Display status of migrations'
task :status => [:environment, :load_config] do
unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
- puts 'Schema migrations table does not exist yet.'
- next # means "return" for rake task
+ abort 'Schema migrations table does not exist yet.'
end
db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
db_list.map! { |version| "%.3d" % version }
@@ -278,7 +277,7 @@ db_namespace = namespace :db do
db_namespace['structure:dump'].reenable
end
- # desc "Recreate the databases from the structure.sql file"
+ desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
ActiveRecord::Tasks::DatabaseTasks.load_schema(:sql, ENV['DB_STRUCTURE'])
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 03b5bdc46c..0eec6774a0 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,3 +1,5 @@
+require 'thread'
+
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
@@ -22,11 +24,11 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
- ar.reflections = ar.reflections.merge(name => reflection)
+ ar.reflections = ar.reflections.merge(name.to_s => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
- ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection)
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end
# \Reflection enables to interrogate Active Record classes and objects
@@ -48,7 +50,7 @@ module ActiveRecord
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
- aggregate_reflections[aggregation]
+ aggregate_reflections[aggregation.to_s]
end
# Returns an array of AssociationReflection objects for all the
@@ -72,7 +74,7 @@ module ActiveRecord
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
- reflections[association]
+ reflections[association.to_s]
end
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -199,6 +201,18 @@ module ActiveRecord
@type = options[:as] && "#{options[:as]}_type"
@foreign_type = options[:foreign_type] || "#{name}_type"
@constructable = calculate_constructable(macro, options)
+ @association_scope_cache = {}
+ @scope_lock = Mutex.new
+ end
+
+ def association_scope_cache(conn, owner)
+ key = conn.prepared_statements
+ if options[:polymorphic]
+ key = [key, owner.read_attribute(@foreign_type)]
+ end
+ @association_scope_cache[key] ||= @scope_lock.synchronize {
+ @association_scope_cache[key] ||= yield
+ }
end
# Returns a new, unsaved instance of the associated class. +attributes+ will
@@ -264,6 +278,25 @@ module ActiveRecord
end
end
+ def check_preloadable!
+ return unless scope
+
+ if scope.arity > 0
+ ActiveSupport::Deprecation.warn <<-WARNING
+The association scope '#{name}' is instance dependent (the scope block takes an argument).
+Preloading happens before the individual instances are created. This means that there is no instance
+being passed to the association scope. This will most likely result in broken or incorrect behavior.
+Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
+ WARNING
+ end
+ end
+ alias :check_eager_loadable! :check_preloadable!
+
+ def join_id_for(owner) #:nodoc:
+ key = (source_macro == :belongs_to) ? foreign_key : active_record_primary_key
+ owner[key]
+ end
+
def through_reflection
nil
end
@@ -449,9 +482,9 @@ module ActiveRecord
end
def derive_class_name
- class_name = name.to_s.camelize
+ class_name = name.to_s
class_name = class_name.singularize if collection?
- class_name
+ class_name.camelize
end
def derive_foreign_key
@@ -617,7 +650,7 @@ module ActiveRecord
# # => [:tag, :tags]
#
def source_reflection_names
- (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
+ options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
end
def source_reflection_name # :nodoc:
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 4d37ac6e2b..d92ff781ee 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+require 'arel/collectors/bind'
module ActiveRecord
# = Active Record Relation
@@ -11,6 +12,7 @@ module ActiveRecord
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
:reverse_order, :distinct, :create_with, :uniq]
+ INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
@@ -223,6 +225,7 @@ module ActiveRecord
# Please see further details in the
# {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
def explain
+ #TODO: Fix for binds.
exec_explain(collecting_queries_for_explain { exec_queries })
end
@@ -238,7 +241,7 @@ module ActiveRecord
# Returns size of the records.
def size
- loaded? ? @records.length : count
+ loaded? ? @records.length : count(:all)
end
# Returns true if there are no records.
@@ -248,8 +251,7 @@ module ActiveRecord
if limit_value == 0
true
else
- # FIXME: This count is not compatible with #select('authors.*') or other select narrows
- c = count
+ c = count(:all)
c.respond_to?(:zero?) ? c.zero? : c.empty?
end
end
@@ -324,7 +326,8 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- @klass.connection.update stmt, 'SQL', bind_values
+ bvs = bind_values + arel.bind_values
+ @klass.connection.update stmt, 'SQL', bvs
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -428,12 +431,21 @@ module ActiveRecord
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
#
- # If a limit scope is supplied, +delete_all+ raises an ActiveRecord error:
+ # If an invalid method is supplied, +delete_all+ raises an ActiveRecord error:
#
# Post.limit(100).delete_all
- # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
def delete_all(conditions = nil)
- raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
+ invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
+ if MULTI_VALUE_METHODS.include?(method)
+ send("#{method}_values").any?
+ else
+ send("#{method}_value")
+ end
+ }
+ if invalid_methods.any?
+ raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
+ end
if conditions
where(conditions).delete_all
@@ -517,11 +529,11 @@ module ActiveRecord
find_with_associations { |rel| relation = rel }
end
- ast = relation.arel.ast
- binds = relation.bind_values.dup
- visitor.accept(ast) do
- connection.quote(*binds.shift.reverse)
- end
+ arel = relation.arel
+ binds = (arel.bind_values + relation.bind_values).dup
+ binds.map! { |bv| connection.quote(*bv.reverse) }
+ collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
+ collect.substitute_binds(binds).join
end
end
@@ -529,16 +541,22 @@ module ActiveRecord
#
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
- def where_values_hash
+ def where_values_hash(relation_table_name = table_name)
equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node|
- node.left.relation.name == table_name
+ node.left.relation.name == relation_table_name
}
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
Hash[equalities.map { |where|
name = where.left.name
- [name, binds.fetch(name.to_s) { where.right }]
+ [name, binds.fetch(name.to_s) {
+ case where.right
+ when Array then where.right.map(&:val)
+ else
+ where.right.val
+ end
+ }]
}]
end
@@ -570,6 +588,8 @@ module ActiveRecord
# Compares two relations for equality.
def ==(other)
case other
+ when Associations::CollectionProxy, AssociationRelation
+ self == other.to_a
when Relation
other.to_sql == to_sql
when Array
@@ -600,7 +620,7 @@ module ActiveRecord
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values)
preload = preload_values
preload += includes_values unless eager_loading?
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 45ffb99868..56cf9bcd27 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -19,6 +19,14 @@ module ActiveRecord
#
# Person.group(:city).count
# # => { 'Rome' => 5, 'Paris' => 3 }
+ #
+ # If +count+ is used with +select+, it will count the selected columns:
+ #
+ # Person.select(:age).count
+ # # => counts the number of different age values
+ #
+ # Note: not all valid +select+ expressions are valid +count+ expressions. The specifics differ
+ # between databases. In invalid cases, an error from the databsae is thrown.
def count(column_name = nil, options = {})
# TODO: Remove options argument as soon we remove support to
# activerecord-deprecated_finders.
@@ -188,7 +196,7 @@ module ActiveRecord
private
def has_include?(column_name)
- eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
+ eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
end
def perform_calculation(operation, column_name, options = {})
@@ -231,15 +239,18 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
# Postgresql doesn't like ORDER BY when there are no GROUP BY
- relation = reorder(nil)
+ relation = unscope(:order)
column_alias = column_name
+ bind_values = nil
+
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
return 0 if relation.limit_value == 0
query_builder = build_count_subquery(relation, column_name, distinct)
+ bind_values = query_builder.bind_values + relation.bind_values
else
column = aggregate_column(column_name)
@@ -249,9 +260,10 @@ module ActiveRecord
relation.select_values = [select_value]
query_builder = relation.arel
+ bind_values = query_builder.bind_values + relation.bind_values
end
- result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
+ result = @klass.connection.select_all(query_builder, nil, bind_values)
row = result.first
value = row && row.values.first
column = result.column_types.fetch(column_alias) do
@@ -385,9 +397,11 @@ module ActiveRecord
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
relation.select_values = [aliased_column]
- subquery = relation.arel.as(subquery_alias)
+ arel = relation.arel
+ subquery = arel.as(subquery_alias)
sm = Arel::SelectManager.new relation.engine
+ sm.bind_values = arel.bind_values
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
sm.project(select_value).from(subquery)
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 21beed332f..50f4d5c7ab 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -40,10 +40,10 @@ module ActiveRecord
BLACKLISTED_ARRAY_METHODS = [
:compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!,
:shuffle!, :slice!, :sort!, :sort_by!, :delete_if,
- :keep_if, :pop, :shift, :delete_at, :compact
+ :keep_if, :pop, :shift, :delete_at, :compact, :select!
].to_set # :nodoc:
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, to: :to_a
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index c2b9dc08fe..db32ae12a8 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -129,9 +129,9 @@ module ActiveRecord
#
def first(limit = nil)
if limit
- find_nth_with_limit(offset_value, limit)
+ find_nth_with_limit(offset_index, limit)
else
- find_nth(:first, offset_value)
+ find_nth(0, offset_index)
end
end
@@ -181,7 +181,7 @@ module ActiveRecord
# Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4)
# Person.where(["user_name = :u", { u: user_name }]).second
def second
- find_nth(:second, offset_value ? offset_value + 1 : 1)
+ find_nth(1, offset_index)
end
# Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -197,7 +197,7 @@ module ActiveRecord
# Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5)
# Person.where(["user_name = :u", { u: user_name }]).third
def third
- find_nth(:third, offset_value ? offset_value + 2 : 2)
+ find_nth(2, offset_index)
end
# Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -213,7 +213,7 @@ module ActiveRecord
# Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6)
# Person.where(["user_name = :u", { u: user_name }]).fourth
def fourth
- find_nth(:fourth, offset_value ? offset_value + 3 : 3)
+ find_nth(3, offset_index)
end
# Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -229,7 +229,7 @@ module ActiveRecord
# Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7)
# Person.where(["user_name = :u", { u: user_name }]).fifth
def fifth
- find_nth(:fifth, offset_value ? offset_value + 4 : 4)
+ find_nth(4, offset_index)
end
# Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -242,10 +242,10 @@ module ActiveRecord
# If no order is defined it will order by primary key.
#
# Person.forty_two # returns the forty-second object fetched by SELECT * FROM people
- # Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44)
+ # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44)
# Person.where(["user_name = :u", { u: user_name }]).forty_two
def forty_two
- find_nth(:forty_two, offset_value ? offset_value + 41 : 41)
+ find_nth(41, offset_index)
end
# Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
@@ -299,11 +299,8 @@ module ActiveRecord
when Array, Hash
relation = relation.where(conditions)
else
- if conditions != :none
- column = columns_hash[primary_key]
- substitute = connection.substitute_at(column, bind_values.length)
- relation = where(table[primary_key].eq(substitute))
- relation.bind_values += [[column, conditions]]
+ unless conditions == :none
+ relation = where(primary_key => conditions)
end
end
@@ -334,6 +331,10 @@ module ActiveRecord
private
+ def offset_index
+ offset_value || 0
+ end
+
def find_with_associations
join_dependency = construct_join_dependency
@@ -347,7 +348,8 @@ module ActiveRecord
if ActiveRecord::NullRelation === relation
[]
else
- rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup)
+ arel = relation.arel
+ rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
join_dependency.instantiate(rows, aliases)
end
end
@@ -468,20 +470,24 @@ module ActiveRecord
end
end
- def find_nth(ordinal, offset)
+ def find_nth(index, offset)
if loaded?
- @records.send(ordinal)
+ @records[index]
else
+ offset += index
@offsets[offset] ||= find_nth_with_limit(offset, 1).first
end
end
def find_nth_with_limit(offset, limit)
- if order_values.empty? && primary_key
- order(arel_table[primary_key].asc).limit(limit).offset(offset).to_a
- else
- limit(limit).offset(offset).to_a
- end
+ relation = if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc)
+ else
+ self
+ end
+
+ relation = relation.offset(offset) unless offset.zero?
+ relation.limit(limit).to_a
end
def find_last
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 182b9ed89c..ac41d0aa80 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -30,6 +30,8 @@ module ActiveRecord
else
other.joins!(*v)
end
+ elsif k == :select
+ other._select!(v)
else
other.send("#{k}!", v)
end
@@ -62,7 +64,13 @@ module ActiveRecord
# expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
# `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
# don't fall through the cracks.
- relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value)
+ unless value.nil? || (value.blank? && false != value)
+ if name == :select
+ relation._select!(*value)
+ else
+ relation.send("#{name}!", *value)
+ end
+ end
end
merge_multi_values
@@ -139,7 +147,6 @@ module ActiveRecord
def merge_single_values
relation.from_value = values[:from] unless relation.from_value
relation.lock_value = values[:lock] unless relation.lock_value
- relation.reverse_order_value = values[:reverse_order]
unless values[:create_with].blank?
relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
@@ -149,7 +156,7 @@ module ActiveRecord
def filter_binds(lhs_binds, removed_wheres)
return lhs_binds if removed_wheres.empty?
- set = Set.new removed_wheres.map { |x| x.left.name }
+ set = Set.new removed_wheres.map { |x| x.left.name.to_s }
lhs_binds.dup.delete_if { |col,_| set.include? col.name }
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 1252af7635..d40f276968 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -113,13 +113,14 @@ module ActiveRecord
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new)
- private
- def self.build(attribute, value)
- handler_for(value).call(attribute, value)
- end
+ def self.build(attribute, value)
+ handler_for(value).call(attribute, value)
+ end
+ private_class_method :build
- def self.handler_for(object)
- @handlers.detect { |klass, _| klass === object }.last
- end
+ def self.handler_for(object)
+ @handlers.detect { |klass, _| klass === object }.last
+ end
+ private_class_method :handler_for
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 0213bca981..416f2305d2 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -64,6 +64,7 @@ module ActiveRecord
#
def #{name}_values=(values) # def select_values=(values)
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ check_cached_relation
@values[:#{name}] = values # @values[:select] = values
end # end
CODE
@@ -81,11 +82,22 @@ module ActiveRecord
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_value=(value) # def readonly_value=(value)
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ check_cached_relation
@values[:#{name}] = value # @values[:readonly] = value
end # end
CODE
end
+ def check_cached_relation # :nodoc:
+ if defined?(@arel) && @arel
+ @arel = nil
+ ActiveSupport::Deprecation.warn <<-WARNING
+Modifying already cached Relation. The cache will be reset.
+Use a cloned Relation to prevent this warning.
+WARNING
+ end
+ end
+
def create_with_value # :nodoc:
@values[:create_with] || {}
end
@@ -235,11 +247,11 @@ module ActiveRecord
to_a.select { |*block_args| yield(*block_args) }
else
raise ArgumentError, 'Call this with at least one field' if fields.empty?
- spawn.select!(*fields)
+ spawn._select!(*fields)
end
end
- def select!(*fields) # :nodoc:
+ def _select!(*fields) # :nodoc:
fields.flatten!
fields.map! do |field|
klass.attribute_alias?(field) ? klass.attribute_alias(field) : field
@@ -825,7 +837,9 @@ module ActiveRecord
end
def reverse_order! # :nodoc:
- self.reverse_order_value = !reverse_order_value
+ orders = order_values.uniq
+ orders.reject!(&:blank?)
+ self.order_values = reverse_sql_order(orders)
self
end
@@ -841,7 +855,7 @@ module ActiveRecord
build_joins(arel, joins_values.flatten) unless joins_values.empty?
- collapse_wheres(arel, (where_values - ['']).uniq)
+ collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
@@ -858,6 +872,15 @@ module ActiveRecord
arel.from(build_from) if from_value
arel.lock(lock_value) if lock_value
+ # Reorder bind indexes if joins produced bind values
+ if arel.bind_values.any?
+ bvs = arel.bind_values + bind_values
+ arel.ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i|
+ column = bvs[i].first
+ bp.replace connection.substitute_at(column, i)
+ end
+ end
+
arel
end
@@ -871,8 +894,9 @@ module ActiveRecord
case scope
when :order
- self.reverse_order_value = false
result = []
+ when :where
+ self.bind_values = []
else
result = [] unless single_val_method
end
@@ -923,18 +947,16 @@ module ActiveRecord
def build_where(opts, other = [])
case opts
when String, Array
- #TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
- values = Hash === other.first ? other.first.values : other
-
- values.grep(ActiveRecord::Relation) do |rel|
- self.bind_values += rel.bind_values
- end
-
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
+ bv_len = bind_values.length
+ tmp_opts, bind_values = create_binds(opts, bv_len)
+ self.bind_values += bind_values
+
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts)
attributes.values.grep(ActiveRecord::Relation) do |rel|
self.bind_values += rel.bind_values
end
@@ -945,6 +967,29 @@ module ActiveRecord
end
end
+ def create_binds(opts, idx)
+ bindable, non_binds = opts.partition do |column, value|
+ case value
+ when String, Integer, ActiveRecord::StatementCache::Substitute
+ @klass.columns_hash.include? column.to_s
+ else
+ false
+ end
+ end
+
+ new_opts = {}
+ binds = []
+
+ bindable.each_with_index do |(column,value), index|
+ binds.push [@klass.columns_hash[column.to_s], value]
+ new_opts[column] = connection.substitute_at(column, index + idx)
+ end
+
+ non_binds.each { |column,value| new_opts[column] = value }
+
+ [new_opts, binds]
+ end
+
def build_from
opts, name = from_value
case opts
@@ -986,9 +1031,12 @@ module ActiveRecord
join_list
)
- joins = join_dependency.join_constraints stashed_association_joins
+ join_infos = join_dependency.join_constraints stashed_association_joins
- joins.each { |join| manager.from(join) }
+ join_infos.each do |info|
+ info.joins.each { |join| manager.from(join) }
+ manager.bind_values.concat info.binds
+ end
manager.join_sources.concat(join_list)
@@ -1031,7 +1079,6 @@ module ActiveRecord
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)
- orders = reverse_sql_order(orders) if reverse_order_value
arel.order(*orders) unless orders.empty?
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 2552cbd234..57d66bce4b 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -58,6 +58,9 @@ module ActiveRecord
# Post.order('id asc').only(:where) # discards the order condition
# Post.order('id asc').only(:where, :order) # uses the specified order
def only(*onlies)
+ if onlies.any? { |o| o == :where }
+ onlies << :bind
+ end
relation_with values.slice(*onlies)
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 5a71c13d91..1aa93ffbb3 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -92,7 +92,7 @@ module ActiveRecord
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
- connection.visitor.accept b
+ connection.visitor.compile b
}.join(' AND ')
end
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -107,6 +107,13 @@ module ActiveRecord
end.join(', ')
end
+ # Sanitizes a +string+ so that it is safe to use within a sql
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
+ def sanitize_sql_like(string, escape_character = "\\")
+ pattern = Regexp.union(escape_character, "%", "_")
+ string.gsub(pattern) { |x| [escape_character, x].join }
+ end
+
# Accepts an array of conditions. The array has each value
# sanitized and interpolated into the SQL statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index dd4ee0c4a0..aece446384 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -14,13 +14,87 @@ module ActiveRecord
# The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
# Database is queried when +to_a+ is called on the relation.
class StatementCache
- def initialize
- @relation = yield
- raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
+ class Substitute; end
+
+ class Query
+ def initialize(sql)
+ @sql = sql
+ end
+
+ def sql_for(binds, connection)
+ @sql
+ end
+ end
+
+ class PartialQuery < Query
+ def initialize values
+ @values = values
+ @indexes = values.each_with_index.find_all { |thing,i|
+ Arel::Nodes::BindParam === thing
+ }.map(&:last)
+ end
+
+ def sql_for(binds, connection)
+ val = @values.dup
+ binds = binds.dup
+ @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
+ val.join
+ end
+ end
+
+ def self.query(visitor, ast)
+ Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
+ end
+
+ def self.partial_query(visitor, ast, collector)
+ collected = visitor.accept(ast, collector).value
+ PartialQuery.new collected
+ end
+
+ class Params
+ def bind; Substitute.new; end
end
- def execute
- @relation.dup.to_a
+ class BindMap
+ def initialize(bind_values)
+ @indexes = []
+ @bind_values = bind_values
+
+ bind_values.each_with_index do |(_, value), i|
+ if Substitute === value
+ @indexes << i
+ end
+ end
+ end
+
+ def bind(values)
+ bvs = @bind_values.map { |pair| pair.dup }
+ @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
+ bvs
+ end
+ end
+
+ attr_reader :bind_map, :query_builder
+
+ def self.create(connection, block = Proc.new)
+ relation = block.call Params.new
+ bind_map = BindMap.new relation.bind_values
+ query_builder = connection.cacheable_query relation.arel
+ new query_builder, bind_map
+ end
+
+ def initialize(query_builder, bind_map)
+ @query_builder = query_builder
+ @bind_map = bind_map
+ end
+
+ def execute(params, klass, connection)
+ bind_values = bind_map.bind params
+
+ sql = query_builder.sql_for bind_values, connection
+
+ klass.find_by_sql sql, bind_values
end
+ alias :call :execute
end
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 79a6ccbda0..7014bc6d45 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -66,8 +66,9 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :stored_attributes, instance_accessor: false
- self.stored_attributes = {}
+ class << self
+ attr_accessor :local_stored_attributes
+ end
end
module ClassMethods
@@ -93,9 +94,9 @@ module ActiveRecord
# assign new store attribute and create new hash to ensure that each class in the hierarchy
# has its own hash of stored attributes.
- self.stored_attributes = {} if self.stored_attributes.blank?
- self.stored_attributes[store_attribute] ||= []
- self.stored_attributes[store_attribute] |= keys
+ self.local_stored_attributes ||= {}
+ self.local_stored_attributes[store_attribute] ||= []
+ self.local_stored_attributes[store_attribute] |= keys
end
def _store_accessors_module
@@ -105,6 +106,14 @@ module ActiveRecord
mod
end
end
+
+ def stored_attributes
+ parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
+ if self.local_stored_attributes
+ parent.merge!(self.local_stored_attributes) { |k, a, b| a | b }
+ end
+ parent
+ end
end
protected
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 6ce0495f6f..168b338b97 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# Example usage of +DatabaseTasks+ outside Rails could look as such:
#
# include ActiveRecord::Tasks
- # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
+ # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
# DatabaseTasks.db_dir = 'db'
# # other settings...
#
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 17f76b63b3..17d1ae1ba0 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -1,5 +1,3 @@
-require 'thread'
-
module ActiveRecord
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
@@ -295,7 +293,7 @@ module ActiveRecord
def committed! #:nodoc:
run_callbacks :commit if destroyed? || persisted?
ensure
- @_start_transaction_state.clear
+ force_clear_transaction_record_state
end
# Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record
@@ -328,7 +326,7 @@ module ActiveRecord
begin
status = yield
rescue ActiveRecord::Rollback
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ clear_transaction_record_state
status = nil
end
@@ -355,7 +353,12 @@ module ActiveRecord
# Clear the new record state and id of a record.
def clear_transaction_record_state #:nodoc:
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
- @_start_transaction_state.clear if @_start_transaction_state[:level] < 1
+ force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
+ end
+
+ # Force to clear the transaction record state.
+ def force_clear_transaction_record_state #:nodoc:
+ @_start_transaction_state.clear
end
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 71c71cb4b1..ee080451a9 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -93,7 +93,7 @@ module ActiveRecord
end
def map_enum_attribute(klass, attribute, value)
- mapping = klass.enum_mapping_for(attribute.to_s)
+ mapping = klass.defined_enums[attribute.to_s]
value = mapping[value] if value && mapping
value
end
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 59324c4857..64cde143a1 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -29,6 +29,7 @@ module ActiveRecord
@columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
options[:default],
+ lookup_cast_type(sql_type.to_s),
sql_type.to_s,
options[:null])
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 90953ce6cd..778c4ed7e5 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -46,9 +46,7 @@ module ActiveRecord
@connection.add_index :accounts, :firm_id, :name => idx_name
indexes = @connection.indexes("accounts")
assert_equal "accounts", indexes.first.table
- # OpenBase does not have the concept of a named index
- # Indexes are merely properties of columns.
- assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter)
+ assert_equal idx_name, indexes.first.name
assert !indexes.first.unique
assert_equal ["firm_id"], indexes.first.columns
else
@@ -127,14 +125,12 @@ module ActiveRecord
assert_equal 1, Movie.create(:name => 'fight club').id
end
- if ActiveRecord::Base.connection.adapter_name != "FrontBase"
- def test_reset_table_with_non_integer_pk
- Subscriber.delete_all
- Subscriber.connection.reset_pk_sequence! 'subscribers'
- sub = Subscriber.new(:name => 'robert drake')
- sub.id = 'bob drake'
- assert_nothing_raised { sub.save! }
- end
+ def test_reset_table_with_non_integer_pk
+ Subscriber.delete_all
+ Subscriber.connection.reset_pk_sequence! 'subscribers'
+ sub = Subscriber.new(:name => 'robert drake')
+ sub.id = 'bob drake'
+ assert_nothing_raised { sub.save! }
end
end
@@ -144,7 +140,7 @@ module ActiveRecord
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
end
end
-
+
unless current_adapter?(:SQLite3Adapter)
def test_foreign_key_violations_are_translated_to_specific_exception
assert_raises(ActiveRecord::InvalidForeignKey) do
@@ -157,7 +153,7 @@ module ActiveRecord
end
end
end
-
+
def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
klass_has_fk = Class.new(ActiveRecord::Base) do
self.table_name = 'fk_test_has_fk'
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 412efa22ff..4c90d06732 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -151,6 +151,14 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_sql_mode_variable_overides_strict_mode
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
+ result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode'
+ assert_not_equal [['STRICT_ALL_TABLES']], result.rows
+ end
+ end
+
def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb
index 3d1330efb8..d8a954efa8 100644
--- a/activerecord/test/cases/adapters/mysql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb
@@ -9,13 +9,13 @@ module ActiveRecord
end
def test_type_cast_true
- c = Column.new(nil, 1, 'boolean')
+ c = Column.new(nil, 1, Type::Boolean.new)
assert_equal 1, @conn.type_cast(true, nil)
assert_equal 1, @conn.type_cast(true, c)
end
def test_type_cast_false
- c = Column.new(nil, 1, 'boolean')
+ c = Column.new(nil, 1, Type::Boolean.new)
assert_equal 0, @conn.type_cast(false, nil)
assert_equal 0, @conn.type_cast(false, c)
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index 807a7a155e..87c5277e64 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -17,6 +17,44 @@ module ActiveRecord
self.table_name = "#{db}.#{table}"
def self.name; 'Post'; end
end
+
+ @connection.create_table "mysql_doubles"
+ end
+
+ teardown do
+ @connection.execute "drop table if exists mysql_doubles"
+ end
+
+ class MysqlDouble < ActiveRecord::Base
+ self.table_name = "mysql_doubles"
+ end
+
+ def test_float_limits
+ @connection.add_column :mysql_doubles, :float_no_limit, :float
+ @connection.add_column :mysql_doubles, :float_short, :float, limit: 5
+ @connection.add_column :mysql_doubles, :float_long, :float, limit: 53
+
+ @connection.add_column :mysql_doubles, :float_23, :float, limit: 23
+ @connection.add_column :mysql_doubles, :float_24, :float, limit: 24
+ @connection.add_column :mysql_doubles, :float_25, :float, limit: 25
+ MysqlDouble.reset_column_information
+
+ column_no_limit = MysqlDouble.columns.find { |c| c.name == 'float_no_limit' }
+ column_short = MysqlDouble.columns.find { |c| c.name == 'float_short' }
+ column_long = MysqlDouble.columns.find { |c| c.name == 'float_long' }
+
+ column_23 = MysqlDouble.columns.find { |c| c.name == 'float_23' }
+ column_24 = MysqlDouble.columns.find { |c| c.name == 'float_24' }
+ column_25 = MysqlDouble.columns.find { |c| c.name == 'float_25' }
+
+ # Mysql floats are precision 0..24, Mysql doubles are precision 25..53
+ assert_equal 24, column_no_limit.limit
+ assert_equal 24, column_short.limit
+ assert_equal 53, column_long.limit
+
+ assert_equal 24, column_23.limit
+ assert_equal 24, column_24.limit
+ assert_equal 53, column_25.limit
end
def test_schema
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 182d9409c7..65f50e77bb 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -77,6 +77,14 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_sql_mode_variable_overides_strict_mode
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
+ result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode'
+ assert_not_equal [['STRICT_ALL_TABLES']], result.rows
+ end
+ end
+
def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 1cd356e868..675703caa1 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -9,15 +9,15 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
assert_match %r(developers |.* const), explain
end
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain
assert_match %r(audit_logs |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 18dd4a6de8..c20030ca64 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -16,7 +16,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
t.integer 'ratings', array: true
end
end
- @column = PgArray.columns.find { |c| c.name == 'tags' }
+ @column = PgArray.columns_hash['tags']
end
teardown do
@@ -61,10 +61,10 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
def test_change_column_with_array
@connection.add_column :pg_arrays, :snippets, :string, array: true, default: []
- @connection.change_column :pg_arrays, :snippets, :text, array: true, default: "{}"
+ @connection.change_column :pg_arrays, :snippets, :text, array: true, default: []
PgArray.reset_column_information
- column = PgArray.columns.find { |c| c.name == 'snippets' }
+ column = PgArray.columns_hash['snippets']
assert_equal :text, column.type
assert_equal [], column.default
@@ -80,6 +80,14 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
end
end
+ def test_change_column_default_with_array
+ @connection.change_column_default :pg_arrays, :tags, []
+
+ PgArray.reset_column_information
+ column = PgArray.columns_hash['tags']
+ assert_equal [], column.default
+ end
+
def test_type_cast_array
data = '{1,2,3}'
oid_type = @column.instance_variable_get('@oid_type').subtype
@@ -101,17 +109,32 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal(['1', '2'], x.ratings)
end
- def test_rewrite
+ def test_select_with_strings
@connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')"
x = PgArray.first
- x.tags = ['1','2','3','4']
- assert x.save!
+ assert_equal(['1','2','3'], x.tags)
end
- def test_select
+ def test_rewrite_with_strings
@connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')"
x = PgArray.first
- assert_equal(['1','2','3'], x.tags)
+ x.tags = ['1','2','3','4']
+ x.save!
+ assert_equal ['1','2','3','4'], x.reload.tags
+ end
+
+ def test_select_with_integers
+ @connection.execute "insert into pg_arrays (ratings) VALUES ('{1,2,3}')"
+ x = PgArray.first
+ assert_equal([1, 2, 3], x.ratings)
+ end
+
+ def test_rewrite_with_integers
+ @connection.execute "insert into pg_arrays (ratings) VALUES ('{1,2,3}')"
+ x = PgArray.first
+ x.ratings = [2, '3', 4]
+ x.save!
+ assert_equal [2, 3, 4], x.reload.ratings
end
def test_multi_dimensional_with_strings
@@ -175,6 +198,14 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal [], pg_array.reload.tags
end
+ def test_escaping
+ unknown = 'foo\\",bar,baz,\\'
+ tags = ["hello_#{unknown}"]
+ ar = PgArray.create!(tags: tags)
+ ar.reload
+ assert_equal tags, ar.tags
+ end
+
private
def assert_cycle field, array
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index c3394d7712..fadadfa57c 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -19,7 +19,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
end
end
end
- @column = ByteaDataType.columns.find { |c| c.name == 'payload' }
+ @column = ByteaDataType.columns_hash['payload']
assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
end
@@ -70,6 +70,23 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
assert_equal(data, record.payload)
end
+ def test_via_to_sql
+ data = "'\u001F\\"
+ ByteaDataType.create(payload: data)
+ sql = ByteaDataType.where(payload: data).select(:payload).to_sql
+ result = @connection.query(sql)
+ assert_equal([[data]], result)
+ end
+
+ def test_via_to_sql_with_complicating_connection
+ Thread.new do
+ other_conn = ActiveRecord::Base.connection
+ other_conn.execute('SET standard_conforming_strings = off')
+ end.join
+
+ test_via_to_sql
+ end
+
def test_write_binary
data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log'))
assert(data.size > 1)
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index 948bf49a54..8493050726 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -50,7 +50,7 @@ if ActiveRecord::Base.connection.supports_extensions?
t.citext 'username'
end
Citext.reset_column_information
- column = Citext.columns.find { |c| c.name == 'username' }
+ column = Citext.columns_hash['username']
assert_equal :citext, column.type
raise ActiveRecord::Rollback # reset the schema change
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index 224b1b770b..f01717d1a7 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -1,19 +1,19 @@
# -*- coding: utf-8 -*-
require "cases/helper"
+require 'support/connection_helper'
require 'active_record/base'
require 'active_record/connection_adapters/postgresql_adapter'
-class PostgresqlCompositeTest < ActiveRecord::TestCase
+module PostgresqlCompositeBehavior
+ include ConnectionHelper
+
class PostgresqlComposite < ActiveRecord::Base
self.table_name = "postgresql_composites"
end
- teardown do
- @connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
- @connection.execute 'DROP TYPE IF EXISTS full_address'
- end
-
def setup
+ super
+
@connection = ActiveRecord::Base.connection
@connection.transaction do
@connection.execute <<-SQL
@@ -29,9 +29,27 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
end
end
+ def teardown
+ super
+
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
+ @connection.execute 'DROP TYPE IF EXISTS full_address'
+ reset_connection
+ PostgresqlComposite.reset_column_information
+ end
+end
+
+# Composites are mapped to `OID::Identity` by default. The user is informed by a warning like:
+# "unknown OID 5653508: failed to recognize type of 'address'. It will be treated as String."
+# To take full advantage of composite types, we suggest you register your own +OID::Type+.
+# See PostgresqlCompositeWithCustomOIDTest
+class PostgresqlCompositeTest < ActiveRecord::TestCase
+ include PostgresqlCompositeBehavior
+
def test_column
+ ensure_warning_is_issued
+
column = PostgresqlComposite.columns_hash["address"]
- # TODO: Composite columns should have a type
assert_nil column.type
assert_equal "full_address", column.sql_type
assert_not column.number?
@@ -41,6 +59,8 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
end
def test_composite_mapping
+ ensure_warning_is_issued
+
@connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));"
composite = PostgresqlComposite.first
assert_equal "(Paris,Champs-Élysées)", composite.address
@@ -50,4 +70,70 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
assert_equal '(Paris,"Rue Basse")', composite.reload.address
end
+
+ private
+ def ensure_warning_is_issued
+ warning = capture(:stderr) do
+ PostgresqlComposite.columns_hash
+ end
+ assert_match(/unknown OID \d+: failed to recognize type of 'address'\. It will be treated as String\./, warning)
+ end
+end
+
+class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
+ include PostgresqlCompositeBehavior
+
+ class FullAddressType < ActiveRecord::ConnectionAdapters::Type::Value
+ def type; :full_address end
+
+ def type_cast(value)
+ if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/
+ FullAddress.new($1, $2)
+ end
+ end
+
+ def type_cast_for_write(value)
+ "(#{value.city},#{value.street})"
+ end
+ end
+
+ FullAddress = Struct.new(:city, :street)
+
+ def setup
+ super
+
+ @registration = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID
+ @registration.register_type "full_address", FullAddressType.new
+ end
+
+ def teardown
+ super
+
+ # there is currently no clean way to unregister a OID::Type
+ @registration::NAMES.delete("full_address")
+ end
+
+ def test_column
+ column = PostgresqlComposite.columns_hash["address"]
+ assert_equal :full_address, column.type
+ assert_equal "full_address", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_composite_mapping
+ @connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));"
+ composite = PostgresqlComposite.first
+ assert_equal "Paris", composite.address.city
+ assert_equal "Champs-Élysées", composite.address.street
+
+ composite.address = FullAddress.new("Paris", "Rue Basse")
+ skip "Saving with custom OID type is currently not supported."
+ composite.save!
+
+ assert_equal 'Paris', composite.reload.address.city
+ assert_equal 'Rue Basse', composite.reload.address.street
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index e7dda1a1af..826d322007 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/ddl_helper'
class PostgresqlArray < ActiveRecord::Base
end
@@ -6,9 +7,6 @@ end
class PostgresqlTsvector < ActiveRecord::Base
end
-class PostgresqlMoney < ActiveRecord::Base
-end
-
class PostgresqlNumber < ActiveRecord::Base
end
@@ -35,22 +33,17 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
- @connection.execute("set lc_monetary = 'C'")
-
- @connection.execute("INSERT INTO postgresql_arrays (id, commission_by_quarter, nicknames) VALUES (1, '{35000,21000,18000,17000}', '{foo,bar,baz}')")
- @first_array = PostgresqlArray.find(1)
@connection.execute("INSERT INTO postgresql_tsvectors (id, text_vector) VALUES (1, ' ''text'' ''vector'' ')")
@first_tsvector = PostgresqlTsvector.find(1)
- @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (1, '567.89'::money)")
- @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (2, '-567.89'::money)")
- @first_money = PostgresqlMoney.find(1)
- @second_money = PostgresqlMoney.find(2)
-
@connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (1, 123.456, 123456.789)")
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (2, '-Infinity', 'Infinity')")
+ @connection.execute("INSERT INTO postgresql_numbers (id, single, double) VALUES (3, 123.456, 'NaN')")
@first_number = PostgresqlNumber.find(1)
+ @second_number = PostgresqlNumber.find(2)
+ @third_number = PostgresqlNumber.find(3)
@connection.execute("INSERT INTO postgresql_times (id, time_interval, scaled_time_interval) VALUES (1, '1 year 2 days ago', '3 weeks ago')")
@first_time = PostgresqlTime.find(1)
@@ -68,31 +61,14 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
teardown do
- [PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress,
+ [PostgresqlTsvector, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress,
PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone].each(&:delete_all)
end
- def test_array_escaping
- unknown = %(foo\\",bar,baz,\\)
- nicknames = ["hello_#{unknown}"]
- ar = PostgresqlArray.create!(nicknames: nicknames, id: 100)
- ar.reload
- assert_equal nicknames, ar.nicknames
- end
-
- def test_data_type_of_array_types
- assert_equal :integer, @first_array.column_for_attribute(:commission_by_quarter).type
- assert_equal :text, @first_array.column_for_attribute(:nicknames).type
- end
-
def test_data_type_of_tsvector_types
assert_equal :tsvector, @first_tsvector.column_for_attribute(:text_vector).type
end
- def test_data_type_of_money_types
- assert_equal :decimal, @first_money.column_for_attribute(:wealth).type
- end
-
def test_data_type_of_number_types
assert_equal :float, @first_number.column_for_attribute(:single).type
assert_equal :float, @first_number.column_for_attribute(:double).type
@@ -118,28 +94,10 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
- def test_array_values
- assert_equal [35000,21000,18000,17000], @first_array.commission_by_quarter
- assert_equal ['foo','bar','baz'], @first_array.nicknames
- end
-
def test_tsvector_values
assert_equal "'text' 'vector'", @first_tsvector.text_vector
end
- def test_money_values
- assert_equal 567.89, @first_money.wealth
- assert_equal(-567.89, @second_money.wealth)
- end
-
- def test_money_type_cast
- column = PostgresqlMoney.columns.find { |c| c.name == 'wealth' }
- assert_equal(12345678.12, column.type_cast("$12,345,678.12"))
- assert_equal(12345678.12, column.type_cast("$12.345.678,12"))
- assert_equal(-1.15, column.type_cast("-$1.15"))
- assert_equal(-2.25, column.type_cast("($2.25)"))
- end
-
def test_update_tsvector
new_text_vector = "'new' 'text' 'vector'"
@first_tsvector.text_vector = new_text_vector
@@ -154,6 +112,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
def test_number_values
assert_equal 123.456, @first_number.single
assert_equal 123456.789, @first_number.double
+ assert_equal(-::Float::INFINITY, @second_number.single)
+ assert_equal ::Float::INFINITY, @second_number.double
+ assert_same ::Float::NAN, @third_number.double
end
def test_time_values
@@ -179,38 +140,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal 1234, @first_oid.obj_id
end
- def test_update_integer_array
- new_value = [32800,95000,29350,17000]
- @first_array.commission_by_quarter = new_value
- assert @first_array.save
- assert @first_array.reload
- assert_equal new_value, @first_array.commission_by_quarter
- @first_array.commission_by_quarter = new_value
- assert @first_array.save
- assert @first_array.reload
- assert_equal new_value, @first_array.commission_by_quarter
- end
-
- def test_update_text_array
- new_value = ['robby','robert','rob','robbie']
- @first_array.nicknames = new_value
- assert @first_array.save
- assert @first_array.reload
- assert_equal new_value, @first_array.nicknames
- @first_array.nicknames = new_value
- assert @first_array.save
- assert @first_array.reload
- assert_equal new_value, @first_array.nicknames
- end
-
- def test_update_money
- new_value = BigDecimal.new('123.45')
- @first_money.wealth = new_value
- assert @first_money.save
- assert @first_money.reload
- assert_equal new_value, @first_money.wealth
- end
-
def test_update_number
new_single = 789.012
new_double = 789012.345
@@ -308,3 +237,25 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@connection.reconnect!
end
end
+
+class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase
+ include DdlHelper
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_name_column_type
+ with_example_table @connection, 'ex', 'data name' do
+ column = @connection.columns('ex').find { |col| col.name == 'data' }
+ assert_equal :string, column.type
+ end
+ end
+
+ def test_char_column_type
+ with_example_table @connection, 'ex', 'data "char"' do
+ column = @connection.columns('ex').find { |col| col.name == 'data' }
+ assert_equal :string, column.type
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 214e89dd7f..5286a847a4 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -19,9 +19,6 @@ class PostgresqlDomainTest < ActiveRecord::TestCase
t.column :price, :custom_money
end
end
-
- # reload type map after creating the enum type
- @connection.send(:reload_type_map)
end
teardown do
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 73da5a74ab..4146b117f6 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -21,8 +21,6 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
t.column :current_mood, :mood
end
end
- # reload type map after creating the enum type
- @connection.send(:reload_type_map)
end
teardown do
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 0b61f61572..416f84cb38 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -9,7 +9,7 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
assert_match %(QUERY PLAN), explain
assert_match %(Index Scan using developers_pkey on developers), explain
end
@@ -17,9 +17,9 @@ module ActiveRecord
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
assert_match %(QUERY PLAN), explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
assert_match %(Index Scan using developers_pkey on developers), explain
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
assert_match %(Seq Scan on audit_logs), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
new file mode 100644
index 0000000000..91058f8681
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -0,0 +1,65 @@
+require "cases/helper"
+require "active_record/base"
+require "active_record/connection_adapters/postgresql_adapter"
+
+class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class EnableHstore < ActiveRecord::Migration
+ def change
+ enable_extension "hstore"
+ end
+ end
+
+ class DisableHstore < ActiveRecord::Migration
+ def change
+ disable_extension "hstore"
+ end
+ end
+
+ def setup
+ super
+
+ @connection = ActiveRecord::Base.connection
+
+ unless @connection.supports_extensions?
+ return skip("no extension support")
+ end
+
+ @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name
+ @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix
+ @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix
+
+ ActiveRecord::Base.table_name_prefix = "p_"
+ ActiveRecord::Base.table_name_suffix = "_s"
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ ActiveRecord::SchemaMigration.table_name = "p_schema_migrations_s"
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix
+ ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ ActiveRecord::Migration.verbose = true
+ ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name
+
+ super
+ end
+
+ def test_enable_extension_migration_ignores_prefix_and_suffix
+ @connection.disable_extension("hstore")
+
+ migrations = [EnableHstore.new(nil, 1)]
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ assert @connection.extension_enabled?("hstore"), "extension hstore should be enabled"
+ end
+
+ def test_disable_extension_migration_ignores_prefix_and_suffix
+ @connection.enable_extension("hstore")
+
+ migrations = [DisableHstore.new(nil, 1)]
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ assert_not @connection.extension_enabled?("hstore"), "extension hstore should not be enabled"
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index c24c4b0d56..67a610b459 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -28,7 +28,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
t.hstore 'settings'
end
end
- @column = Hstore.columns.find { |c| c.name == 'tags' }
+ @column = Hstore.columns_hash['tags']
end
teardown do
@@ -78,7 +78,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
t.hstore 'users', default: ''
end
Hstore.reset_column_information
- column = Hstore.columns.find { |c| c.name == 'users' }
+ column = Hstore.columns_hash['users']
assert_equal :hstore, column.type
raise ActiveRecord::Rollback # reset the schema change
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index ee793ffff2..d25f8bf958 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -23,7 +23,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
rescue ActiveRecord::StatementInvalid
skip "do not test on PG without json"
end
- @column = JsonDataType.columns.find { |c| c.name == 'payload' }
+ @column = JsonDataType.columns_hash['payload']
end
teardown do
@@ -57,7 +57,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
t.json 'users', default: '{}'
end
JsonDataType.reset_column_information
- column = JsonDataType.columns.find { |c| c.name == 'users' }
+ column = JsonDataType.columns_hash['users']
assert_equal :json, column.type
raise ActiveRecord::Rollback # reset the schema change
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
new file mode 100644
index 0000000000..b9770e954c
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -0,0 +1,54 @@
+# encoding: utf-8
+
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlByteaTest < ActiveRecord::TestCase
+ class PostgresqlMoney < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.execute("set lc_monetary = 'C'")
+ end
+
+ def test_column
+ column = PostgresqlMoney.columns_hash["wealth"]
+ assert_equal :decimal, column.type
+ assert_equal "money", column.sql_type
+ assert_equal 2, column.scale
+ assert column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_money_values
+ @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (1, '567.89'::money)")
+ @connection.execute("INSERT INTO postgresql_moneys (id, wealth) VALUES (2, '-567.89'::money)")
+
+ first_money = PostgresqlMoney.find(1)
+ second_money = PostgresqlMoney.find(2)
+ assert_equal 567.89, first_money.wealth
+ assert_equal(-567.89, second_money.wealth)
+ end
+
+ def test_money_type_cast
+ column = PostgresqlMoney.columns_hash['wealth']
+ assert_equal(12345678.12, column.type_cast("$12,345,678.12"))
+ assert_equal(12345678.12, column.type_cast("$12.345.678,12"))
+ assert_equal(-1.15, column.type_cast("-$1.15"))
+ assert_equal(-2.25, column.type_cast("($2.25)"))
+ end
+
+ def test_create_and_update_money
+ money = PostgresqlMoney.create(wealth: "987.65")
+ assert_equal 987.65, money.wealth
+
+ new_value = BigDecimal.new('123.45')
+ money.wealth = new_value
+ money.save!
+ money.reload
+ assert_equal new_value, money.wealth
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 49d8ec238d..49f5ec250f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,11 +1,13 @@
# encoding: utf-8
require "cases/helper"
require 'support/ddl_helper'
+require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::TestCase
include DdlHelper
+ include ConnectionHelper
def setup
@connection = ActiveRecord::Base.connection
@@ -357,6 +359,60 @@ module ActiveRecord
end
end
+ def test_reload_type_map_for_newly_defined_types
+ @connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')"
+ result = @connection.select_all "SELECT 'good'::feeling"
+ assert_instance_of(PostgreSQLAdapter::OID::Enum,
+ result.column_types["feeling"])
+ ensure
+ @connection.execute "DROP TYPE IF EXISTS feeling"
+ reset_connection
+ end
+
+ def test_only_reload_type_map_once_for_every_unknown_type
+ silence_warnings do
+ assert_queries 2, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyelement"
+ end
+ assert_queries 1, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyelement"
+ end
+ assert_queries 2, ignore_none: true do
+ @connection.select_all "SELECT NULL::anyarray"
+ end
+ end
+ ensure
+ reset_connection
+ end
+
+ def test_only_warn_on_first_encounter_of_unknown_oid
+ warning = capture(:stderr) {
+ @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "SELECT NULL::anyelement"
+ @connection.select_all "SELECT NULL::anyelement"
+ }
+ assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning)
+ ensure
+ reset_connection
+ end
+
+ def test_unparsed_defaults_are_at_least_set_when_saving
+ with_example_table "id SERIAL PRIMARY KEY, number INTEGER NOT NULL DEFAULT (4 + 4) * 2 / 4" do
+ number_klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'ex'
+ end
+ column = number_klass.columns_hash["number"]
+ assert_nil column.default
+ assert_nil column.default_function
+
+ first_number = number_klass.new
+ assert_nil first_number.number
+
+ first_number.save!
+ assert_equal 4, first_number.reload.number
+ end
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 51846e22d9..218c59247e 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -10,13 +10,13 @@ module ActiveRecord
end
def test_type_cast_true
- c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean')
+ c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
assert_equal 't', @conn.type_cast(true, nil)
assert_equal 't', @conn.type_cast(true, c)
end
def test_type_cast_false
- c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean')
+ c = PostgreSQLColumn.new(nil, 1, Type::Boolean.new, 'boolean')
assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 'f', @conn.type_cast(false, c)
end
@@ -47,9 +47,9 @@ module ActiveRecord
def test_quote_cast_numeric
fixnum = 666
- c = PostgreSQLColumn.new(nil, nil, OID::String.new, 'varchar')
+ c = PostgreSQLColumn.new(nil, nil, Type::String.new, 'varchar')
assert_equal "'666'", @conn.quote(fixnum, c)
- c = PostgreSQLColumn.new(nil, nil, OID::Text.new, 'text')
+ c = PostgreSQLColumn.new(nil, nil, Type::Text.new, 'text')
assert_equal "'666'", @conn.quote(fixnum, c)
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 57c7da2657..060b17d071 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -34,7 +34,6 @@ _SQL
@connection.add_column 'postgresql_ranges', 'float_range', 'floatrange'
end
- @connection.send :reload_type_map
PostgresqlRange.reset_column_information
rescue ActiveRecord::StatementInvalid
skip "do not test on PG without range"
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 11ec7599a3..b9e296ed8f 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -50,6 +50,16 @@ class SchemaTest < ActiveRecord::TestCase
self.table_name = 'things'
end
+ class Song < ActiveRecord::Base
+ self.table_name = "music.songs"
+ has_and_belongs_to_many :albums
+ end
+
+ class Album < ActiveRecord::Base
+ self.table_name = "music.albums"
+ has_and_belongs_to_many :songs
+ end
+
def setup
@connection = ActiveRecord::Base.connection
@connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
@@ -109,6 +119,22 @@ class SchemaTest < ActiveRecord::TestCase
assert !@connection.schema_names.include?("test_schema3")
end
+ def test_habtm_table_name_with_schema
+ ActiveRecord::Base.connection.execute <<-SQL
+ DROP SCHEMA IF EXISTS music CASCADE;
+ CREATE SCHEMA music;
+ CREATE TABLE music.albums (id serial primary key);
+ CREATE TABLE music.songs (id serial primary key);
+ CREATE TABLE music.albums_songs (album_id integer, song_id integer);
+ SQL
+
+ song = Song.create
+ album = Album.create
+ assert_equal song, Song.includes(:albums).references(:albums).first
+ ensure
+ ActiveRecord::Base.connection.execute "DROP SCHEMA music CASCADE;"
+ end
+
def test_raise_drop_schema_with_nonexisting_schema
assert_raises(ActiveRecord::StatementInvalid) do
@connection.drop_schema "test_schema3"
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
index 9e7b08ef34..e6d7868e9a 100644
--- a/activerecord/test/cases/adapters/postgresql/utils_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
class PostgreSQLUtilsTest < ActiveSupport::TestCase
- include ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils
+ include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
def test_extract_schema_and_table
{
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 9e03ea6bee..40ed0f64a4 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -40,6 +40,21 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
drop_table "uuid_data_type"
end
+ def test_change_column_default
+ @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()"
+ UUIDType.reset_column_information
+ column = UUIDType.columns_hash['thingy']
+ assert_equal "uuid_generate_v1()", column.default_function
+
+ @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
+
+ UUIDType.reset_column_information
+ column = UUIDType.columns_hash['thingy']
+ assert_equal "uuid_generate_v4()", column.default_function
+ ensure
+ UUIDType.reset_column_information
+ end
+
def test_data_type_of_uuid_types
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index ae299697b1..c1c85f8c92 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -20,7 +20,7 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
rescue ActiveRecord::StatementInvalid
skip "do not test on PG without xml"
end
- @column = XmlDataType.columns.find { |c| c.name == 'payload' }
+ @column = XmlDataType.columns_hash['payload']
end
teardown do
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index b227bce680..f1d6119d2e 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -9,15 +9,15 @@ module ActiveRecord
def test_explain_for_one_query
explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index ba89487838..0c4f06d6a9 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -47,13 +47,13 @@ module ActiveRecord
end
def test_type_cast_true
- c = Column.new(nil, 1, 'int')
+ c = Column.new(nil, 1, Type::Integer.new)
assert_equal 't', @conn.type_cast(true, nil)
assert_equal 1, @conn.type_cast(true, c)
end
def test_type_cast_false
- c = Column.new(nil, 1, 'int')
+ c = Column.new(nil, 1, Type::Integer.new)
assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 0, @conn.type_cast(false, c)
end
@@ -61,16 +61,16 @@ module ActiveRecord
def test_type_cast_string
assert_equal '10', @conn.type_cast('10', nil)
- c = Column.new(nil, 1, 'int')
+ c = Column.new(nil, 1, Type::Integer.new)
assert_equal 10, @conn.type_cast('10', c)
- c = Column.new(nil, 1, 'float')
+ c = Column.new(nil, 1, Type::Float.new)
assert_equal 10.1, @conn.type_cast('10.1', c)
- c = Column.new(nil, 1, 'binary')
+ c = Column.new(nil, 1, Type::Binary.new)
assert_equal '10.1', @conn.type_cast('10.1', c)
- c = Column.new(nil, 1, 'date')
+ c = Column.new(nil, 1, Type::Date.new)
assert_equal '10.1', @conn.type_cast('10.1', c)
end
@@ -100,7 +100,7 @@ module ActiveRecord
def quoted_id
"'zomg'"
end
- }
+ }.new
assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) }
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 14aad61ce2..e55525177f 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -182,7 +182,7 @@ module ActiveRecord
def test_quote_binary_column_escapes_it
DualEncoding.connection.execute(<<-eosql)
- CREATE TABLE dual_encodings (
+ CREATE TABLE IF NOT EXISTS dual_encodings (
id integer PRIMARY KEY AUTOINCREMENT,
name varchar(255),
data binary
@@ -192,9 +192,8 @@ module ActiveRecord
binary = DualEncoding.new name: 'いただきます!', data: str
binary.save!
assert_equal str, binary.data
-
ensure
- DualEncoding.connection.drop_table('dual_encodings')
+ DualEncoding.connection.execute('DROP TABLE IF EXISTS dual_encodings')
end
def test_type_cast_should_not_mutate_encoding
@@ -417,6 +416,21 @@ module ActiveRecord
assert @conn.respond_to?(:disable_extension)
end
+ def test_statement_closed
+ db = SQLite3::Database.new(ActiveRecord::Base.
+ configurations['arunit']['database'])
+ statement = SQLite3::Statement.new(db,
+ 'CREATE TABLE statement_test (number integer not null)')
+ statement.stubs(:step).raises(SQLite3::BusyException, 'busy')
+ statement.stubs(:columns).once.returns([])
+ statement.expects(:close).once
+ SQLite3::Statement.stubs(:new).returns(statement)
+
+ assert_raises ActiveRecord::StatementInvalid do
+ @conn.exec_query 'select * from statement_test'
+ end
+ end
+
private
def assert_logged logs
diff --git a/activerecord/test/cases/associations/association_scope_test.rb b/activerecord/test/cases/associations/association_scope_test.rb
index c78b036f53..3e0032ec73 100644
--- a/activerecord/test/cases/associations/association_scope_test.rb
+++ b/activerecord/test/cases/associations/association_scope_test.rb
@@ -9,7 +9,12 @@ module ActiveRecord
scope = AssociationScope.scope(Author.new.association(:welcome_posts),
Author.connection)
wheres = scope.where_values.map(&:right)
+ binds = scope.bind_values.map(&:last)
+ wheres = scope.where_values.map(&:right).reject { |node|
+ Arel::Nodes::BindParam === node
+ }
assert_equal wheres.uniq, wheres
+ assert_equal binds.uniq, binds
end
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 27f6fa575d..9c92dc1141 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -233,13 +233,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_counter
debate = Topic.create("title" => "debate")
- assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
+ assert_equal 0, debate.read_attribute("replies_count"), "No replies yet"
trash = debate.replies.create("title" => "blah!", "content" => "world around!")
- assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
+ assert_equal 1, Topic.find(debate.id).read_attribute("replies_count"), "First reply created"
trash.destroy
- assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
+ assert_equal 0, Topic.find(debate.id).read_attribute("replies_count"), "First reply deleted"
end
def test_belongs_to_counter_with_assigning_nil
@@ -369,6 +369,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_queries(2) { line_item.update amount: 10 }
end
+ def test_belongs_to_with_touch_option_on_empty_update
+ line_item = LineItem.create!
+ Invoice.create!(line_items: [line_item])
+
+ assert_queries(0) { line_item.save }
+ end
+
def test_belongs_to_with_touch_option_on_destroy
line_item = LineItem.create!
Invoice.create!(line_items: [line_item])
@@ -502,6 +509,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 4, topic.replies.size
end
+ def test_concurrent_counter_cache_double_destroy
+ topic = Topic.create :title => "Zoom-zoom-zoom"
+
+ 5.times do
+ topic.replies.create(:title => "re: zoom", :content => "speedy quick!")
+ end
+
+ assert_equal 5, topic.reload[:replies_count]
+ assert_equal 5, topic.replies.size
+
+ reply = topic.replies.first
+ reply_clone = Reply.find(reply.id)
+
+ reply.destroy
+ assert_equal 4, topic.reload[:replies_count]
+
+ reply_clone.destroy
+ assert_equal 4, topic.reload[:replies_count]
+ assert_equal 4, topic.replies.size
+ end
+
def test_custom_counter_cache
reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
assert_equal 0, reply[:replies_count]
@@ -542,6 +570,19 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert companies(:first_client).readonly_firm.readonly?
end
+ def test_test_polymorphic_assignment_foreign_key_type_string
+ comment = Comment.first
+ comment.author = Author.first
+ comment.resource = Member.first
+ comment.save
+
+ assert_equal Comment.all.to_a,
+ Comment.includes(:author).to_a
+
+ assert_equal Comment.all.to_a,
+ Comment.includes(:resource).to_a
+ end
+
def test_polymorphic_assignment_foreign_type_field_updating
# should update when assigning a saved record
sponsor = Sponsor.new
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index cf71bc1597..5b7e462f64 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -150,7 +150,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
"after_removing#{jamis.id}"], activerecord.developers_log
end
- def test_has_and_belongs_to_many_remove_callback_on_clear
+ def test_has_and_belongs_to_many_does_not_fire_callbacks_on_clear
activerecord = projects(:active_record)
assert activerecord.developers_log.empty?
if activerecord.developers_with_callbacks.size == 0
@@ -159,9 +159,9 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
activerecord.reload
assert activerecord.developers_with_callbacks.size == 2
end
- log_array = activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort
+ activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort
assert activerecord.developers_with_callbacks.clear
- assert_equal log_array, activerecord.developers_log.sort
+ assert_predicate activerecord.developers_log, :empty?
end
def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 8c9797861c..ebcf453305 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -534,21 +534,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit_and_conditions
- if current_adapter?(:OpenBaseAdapter)
- posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id").to_a
- else
- posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a
- end
+ posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a
assert_equal 2, posts.size
assert_equal [4,5], posts.collect { |p| p.id }
end
def test_eager_with_has_many_and_limit_and_conditions_array
- if current_adapter?(:OpenBaseAdapter)
- posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id").to_a
- else
- posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a
- end
+ posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a
assert_equal 2, posts.size
assert_equal [4,5], posts.collect { |p| p.id }
end
@@ -826,11 +818,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_with_interpolation
- post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ assert_deprecated do
+ post = Post.includes(:comments_with_interpolated_conditions).find(posts(:welcome).id)
+ assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ end
- post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id)
- assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ assert_deprecated do
+ post = Post.joins(:comments_with_interpolated_conditions).find(posts(:welcome).id)
+ assert_equal [comments(:greetings)], post.comments_with_interpolated_conditions
+ end
end
def test_polymorphic_type_condition
@@ -936,13 +932,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_count_with_include
- if current_adapter?(:SybaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.where("len(comments.body) > 15").references(:comments).count
- elsif current_adapter?(:OpenBaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.where("length(FETCHBLOB(comments.body)) > 15").references(:comments).count
- else
- assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count
- end
+ assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count
end
def test_load_with_sti_sharing_association
@@ -1198,6 +1188,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal authors(:bob), author
end
+ test "preloading with a polymorphic association and using the existential predicate but also using a select" do
+ assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
+
+ assert_nothing_raised do
+ authors(:david).essays.includes(:writer).select(:name).any?
+ end
+ end
+
test "preloading with a polymorphic association and using the existential predicate" do
assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
@@ -1224,4 +1222,23 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 2, author.posts.size
}
end
+
+ test "include instance dependent associations is deprecated" do
+ message = "association scope 'posts_with_signature' is"
+ assert_deprecated message do
+ begin
+ Author.includes(:posts_with_signature).to_a
+ rescue NoMethodError
+ # it's expected that preloading of this association fails
+ end
+ end
+
+ assert_deprecated message do
+ Author.preload(:posts_with_signature).to_a rescue NoMethodError
+ end
+
+ assert_deprecated message do
+ Author.eager_load(:posts_with_signature).to_a
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 366472c6fd..878f1877db 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -21,6 +21,10 @@ require 'models/membership'
require 'models/sponsor'
require 'models/country'
require 'models/treaty'
+require 'models/vertex'
+require 'models/publisher'
+require 'models/publisher/article'
+require 'models/publisher/magazine'
require 'active_support/core_ext/string/conversions'
class ProjectWithAfterCreateHook < ActiveRecord::Base
@@ -82,6 +86,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
country.treaties << treaty
end
+ def test_marshal_dump
+ post = posts :welcome
+ preloaded = Post.includes(:categories).find post.id
+ assert_equal preloaded, Marshal.load(Marshal.dump(preloaded))
+ end
+
def test_should_property_quote_string_primary_keys
setup_data_for_habtm_case
@@ -216,6 +226,24 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers
end
+ def test_habtm_collection_size_from_build
+ devel = Developer.create("name" => "Fred Wu")
+ devel.projects << Project.create("name" => "Grimetime")
+ devel.projects.build
+
+ assert_equal 2, devel.projects.size
+ end
+
+ def test_habtm_collection_size_from_params
+ devel = Developer.new({
+ projects_attributes: {
+ '0' => {}
+ }
+ })
+
+ assert_equal 1, devel.projects.size
+ end
+
def test_build
devel = Developer.find(1)
proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
@@ -819,4 +847,17 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, treasure.rich_people.size
assert_equal person_first_name, rich_person.first_name, 'should not run associated person validation on update when validate: false'
end
+
+ def test_custom_join_table
+ assert_equal 'edges', Vertex.reflect_on_association(:sources).join_table
+ end
+
+ def test_namespaced_habtm
+ magazine = Publisher::Magazine.create
+ article = Publisher::Article.create
+ magazine.articles << article
+ magazine.save
+
+ assert_includes magazine.articles, article
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index c79c0c87c5..5f01352ab4 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -26,6 +26,8 @@ require 'models/reference'
require 'models/job'
require 'models/college'
require 'models/student'
+require 'models/pirate'
+require 'models/ship'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -43,12 +45,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
:people, :posts, :readers, :taggings, :cars, :essays,
- :categorizations, :jobs
+ :categorizations, :jobs, :tags
def setup
Client.destroyed_client_ids.clear
end
+ def test_sti_subselect_count
+ tag = Tag.first
+ len = Post.tagged_with(tag.id).limit(10).size
+ assert_operator len, :>, 0
+ end
+
def test_anonymous_has_many
developer = Class.new(ActiveRecord::Base) {
self.table_name = 'developers'
@@ -131,6 +139,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal(expected_sql, loaded_sql)
end
+ def test_delete_all_on_association_with_nil_dependency_is_the_same_as_not_loaded
+ author = authors :david
+ author.posts.create!(:title => "test", :body => "body")
+ author.reload
+ expected_sql = capture_sql { author.posts.delete_all }
+
+ author.posts.create!(:title => "test", :body => "body")
+ author.reload
+ author.posts.to_a
+ loaded_sql = capture_sql { author.posts.delete_all }
+ assert_equal(expected_sql, loaded_sql)
+ end
+
def test_building_the_associated_object_with_implicit_sti_base_class
firm = DependentFirm.new
company = firm.companies.build
@@ -1864,4 +1885,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
end
+
+ test 'passes custom context validation to validate children' do
+ pirate = FamousPirate.new
+ pirate.famous_ships << ship = FamousShip.new
+
+ assert pirate.valid?
+ assert_not pirate.valid?(:conference)
+ assert_equal "can't be blank", ship.errors[:name].first
+ end
+
+ test 'association with instance dependent scope' do
+ bob = authors(:bob)
+ Post.create!(title: "signed post by bob", body: "stuff", author: authors(:bob))
+ Post.create!(title: "anonymous post", body: "more stuff", author: authors(:bob))
+ assert_equal ["misc post by bob", "other post by bob",
+ "signed post by bob"], bob.posts_with_signature.map(&:title).sort
+
+ assert_equal [], authors(:david).posts_with_signature.map(&:title)
+ end
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 026a7fe635..2e62189e7a 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -698,9 +698,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
[:added, :before, "Roger"],
[:added, :after, "Roger"]
], log.last(4)
-
- post.people_with_callbacks.clear
- assert_equal((%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort)
end
def test_dynamic_find_should_respect_association_include
@@ -1082,7 +1079,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
end
- test "has many through associations on new records use null relations" do
+ def test_has_many_through_associations_on_new_records_use_null_relations
person = Person.new
assert_no_queries do
@@ -1094,7 +1091,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- test "has many through with default scope on the target" do
+ def test_has_many_through_with_default_scope_on_the_target
person = people(:michael)
assert_equal [posts(:thinking)], person.first_posts
@@ -1105,4 +1102,28 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_with_includes_in_through_association_scope
assert_not_empty posts(:welcome).author_address_extra_with_address
end
+
+ def test_insert_records_via_has_many_through_association_with_scope
+ club = Club.create!
+ member = Member.create!
+ Membership.create!(club: club, member: member)
+
+ club.favourites << member
+ assert_equal [member], club.favourites
+
+ club.reload
+ assert_equal [member], club.favourites
+ end
+
+ def test_has_many_through_unscope_default_scope
+ post = Post.create!(:title => 'Beaches', :body => "I like beaches!")
+ Reader.create! :person => people(:david), :post => post
+ LazyReader.create! :person => people(:susan), :post => post
+
+ assert_equal 2, post.people.to_a.size
+ assert_equal 1, post.lazy_people.to_a.size
+
+ assert_equal 2, post.lazy_readers_unscope_skimmers.to_a.size
+ assert_equal 2, post.lazy_people_unscope_skimmers.to_a.size
+ end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 952baaca36..495b43c9e5 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -288,10 +288,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_read_attribute
topic = Topic.new
topic.title = "Don't change the topic"
- assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
+ assert_equal "Don't change the topic", topic.read_attribute("title")
assert_equal "Don't change the topic", topic["title"]
- assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
+ assert_equal "Don't change the topic", topic.read_attribute(:title)
assert_equal "Don't change the topic", topic[:title]
end
@@ -358,10 +358,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
super(attr_name).upcase
end
- assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title")
+ assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute("title")
assert_equal "STOP CHANGING THE TOPIC", topic["title"]
- assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title)
+ assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute(:title)
assert_equal "STOP CHANGING THE TOPIC", topic[:title]
end
@@ -516,7 +516,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_only_time_related_columns_are_meant_to_be_cached_by_default
- expected = %w(datetime timestamp time date).sort
+ expected = %w(datetime time date).sort
assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index f7584c3a51..09892d50ba 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -683,10 +683,23 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
end
+ @ship.pirate.catchphrase = "Changed Catchphrase"
+
assert_raise(RuntimeError) { assert !@pirate.save }
assert_not_nil @pirate.reload.ship
end
+ def test_should_save_changed_has_one_changed_object_if_child_is_saved
+ @pirate.ship.name = "NewName"
+ assert @pirate.save
+ assert_equal "NewName", @pirate.ship.reload.name
+ end
+
+ def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved
+ @pirate.ship.expects(:save).never
+ assert @pirate.save
+ end
+
# belongs_to
def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
assert !@ship.pirate.marked_for_destruction?
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 4969344763..7c7c1fbfbd 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -160,19 +160,11 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_preserving_date_objects
- if current_adapter?(:SybaseAdapter)
- # Sybase ctlib does not (yet?) support the date type; use datetime instead.
- assert_kind_of(
- Time, Topic.find(1).last_read,
- "The last_read attribute should be of the Time class"
- )
- else
- # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
- assert_kind_of(
- Date, Topic.find(1).last_read,
- "The last_read attribute should be of the Date class"
- )
- end
+ # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
+ assert_kind_of(
+ Date, Topic.find(1).last_read,
+ "The last_read attribute should be of the Date class"
+ )
end
def test_previously_changed
@@ -480,8 +472,8 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- # Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter)
+ # Oracle does not have a TIME datatype.
+ unless current_adapter?(:OracleAdapter)
def test_utc_as_time_zone
with_timezone_config default: :utc do
attributes = { "bonus_time" => "5:42:00AM" }
@@ -515,12 +507,7 @@ class BasicsTest < ActiveRecord::TestCase
topic = Topic.find(topic.id)
assert_nil topic.last_read
- # Sybase adapter does not allow nulls in boolean columns
- if current_adapter?(:SybaseAdapter)
- assert topic.approved == false
- else
- assert_nil topic.approved
- end
+ assert_nil topic.approved
end
def test_equality
@@ -551,6 +538,41 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal one, two
end
+ def test_equality_of_relation_and_collection_proxy
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert car.bulbs == Bulb.where(car_id: car.id), 'CollectionProxy should be comparable with Relation'
+ assert Bulb.where(car_id: car.id) == car.bulbs, 'Relation should be comparable with CollectionProxy'
+ end
+
+ def test_equality_of_relation_and_array
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert Bulb.where(car_id: car.id) == car.bulbs.to_a, 'Relation should be comparable with Array'
+ end
+
+ def test_equality_of_relation_and_association_relation
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), 'Relation should be comparable with AssociationRelation'
+ assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), 'AssociationRelation should be comparable with Relation'
+ end
+
+ def test_equality_of_collection_proxy_and_association_relation
+ car = Car.create!
+ car.bulbs.build
+ car.save
+
+ assert_equal car.bulbs, car.bulbs.includes(:car), 'CollectionProxy should be comparable with AssociationRelation'
+ assert_equal car.bulbs.includes(:car), car.bulbs, 'AssociationRelation should be comparable with CollectionProxy'
+ end
+
def test_hashing
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
@@ -650,8 +672,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_attributes_on_dummy_time
- # Oracle, and Sybase do not have a TIME datatype.
- return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
+ # Oracle does not have a TIME datatype.
+ return true if current_adapter?(:OracleAdapter)
with_timezone_config default: :local do
attributes = {
@@ -664,8 +686,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_attributes_on_dummy_time_with_invalid_time
- # Oracle, and Sybase do not have a TIME datatype.
- return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
+ # Oracle does not have a TIME datatype.
+ return true if current_adapter?(:OracleAdapter)
attributes = {
"bonus_time" => "not a time"
@@ -752,8 +774,14 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("c", duped_topic.title)
end
+ DeveloperSalary = Struct.new(:amount)
def test_dup_with_aggregate_of_same_name_as_attribute
- dev = DeveloperWithAggregate.find(1)
+ developer_with_aggregate = Class.new(ActiveRecord::Base) do
+ self.table_name = 'developers'
+ composed_of :salary, :class_name => 'BasicsTest::DeveloperSalary', :mapping => [%w(salary amount)]
+ end
+
+ dev = developer_with_aggregate.find(1)
assert_kind_of DeveloperSalary, dev.salary
dup = nil
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 9a486cf8b8..b41b95309b 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -2,9 +2,9 @@
require "cases/helper"
# Without using prepared statements, it makes no sense to test
-# BLOB data with DB2 or Firebird, because the length of a statement
+# BLOB data with DB2, because the length of a statement
# is limited to 32KB.
-unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
+unless current_adapter?(:DB2Adapter)
require 'models/binary'
class BinaryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index db999f90ab..b8de78934e 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -387,6 +387,20 @@ class CalculationsTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Account.count(1, 2, 3) }
end
+ def test_count_with_order
+ assert_equal 6, Account.order(:credit_limit).count
+ end
+
+ def test_count_with_reverse_order
+ assert_equal 6, Account.order(:credit_limit).reverse_order.count
+ end
+
+ def test_count_with_where_and_order
+ assert_equal 1, Account.where(firm_name: '37signals').count
+ assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).count
+ assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count
+ end
+
def test_should_sum_expression
# Oracle adapter returns floating point value 636.0 after SUM
if current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index c1dd1f1c69..23e6254577 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -12,13 +12,13 @@ module ActiveRecord
end
def test_can_set_coder
- column = Column.new("title", nil, "varchar(20)")
+ column = Column.new("title", nil, Type::String.new, "varchar(20)")
column.coder = YAML
assert_equal YAML, column.coder
end
def test_encoded?
- column = Column.new("title", nil, "varchar(20)")
+ column = Column.new("title", nil, Type::String.new, "varchar(20)")
assert !column.encoded?
column.coder = YAML
@@ -26,7 +26,7 @@ module ActiveRecord
end
def test_type_case_coded_column
- column = Column.new("title", nil, "varchar(20)")
+ column = Column.new("title", nil, Type::String.new, "varchar(20)")
column.coder = YAML
assert_equal "hello", column.type_cast("--- hello")
end
@@ -34,7 +34,7 @@ module ActiveRecord
# Avoid column definitions in create table statements like:
# `title` varchar(255) DEFAULT NULL
def test_should_not_include_default_clause_when_default_is_null
- column = Column.new("title", nil, "varchar(20)")
+ column = Column.new("title", nil, Type::String.new, "varchar(20)")
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -42,7 +42,7 @@ module ActiveRecord
end
def test_should_include_default_clause_when_default_is_present
- column = Column.new("title", "Hello", "varchar(20)")
+ column = Column.new("title", "Hello", Type::String.new, "varchar(20)")
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -50,7 +50,7 @@ module ActiveRecord
end
def test_should_specify_not_null_if_null_option_is_false
- column = Column.new("title", "Hello", "varchar(20)", false)
+ column = Column.new("title", "Hello", Type::String.new, "varchar(20)", false)
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -59,68 +59,68 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = MysqlAdapter::Column.new("title", "a", "binary(1)")
+ binary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "binary(1)")
assert_equal "a", binary_column.default
- varbinary_column = MysqlAdapter::Column.new("title", "a", "varbinary(1)")
+ varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "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
- MysqlAdapter::Column.new("title", "a", "blob")
+ MysqlAdapter::Column.new("title", "a", Type::Binary.new, "blob")
end
assert_raise ArgumentError do
- MysqlAdapter::Column.new("title", "Hello", "text")
+ MysqlAdapter::Column.new("title", "Hello", Type::Text.new)
end
- text_column = MysqlAdapter::Column.new("title", nil, "text")
+ text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)
assert_equal nil, text_column.default
- not_null_text_column = MysqlAdapter::Column.new("title", nil, "text", false)
+ not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new, "text", false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
- blob_column = MysqlAdapter::Column.new("title", nil, "blob")
+ blob_column = MysqlAdapter::Column.new("title", nil, Type::Binary.new, "blob")
assert !blob_column.has_default?
- text_column = MysqlAdapter::Column.new("title", nil, "text")
+ text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new)
assert !text_column.has_default?
end
end
if current_adapter?(:Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = Mysql2Adapter::Column.new("title", "a", "binary(1)")
+ binary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "binary(1)")
assert_equal "a", binary_column.default
- varbinary_column = Mysql2Adapter::Column.new("title", "a", "varbinary(1)")
+ varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "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
- Mysql2Adapter::Column.new("title", "a", "blob")
+ Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "blob")
end
assert_raise ArgumentError do
- Mysql2Adapter::Column.new("title", "Hello", "text")
+ Mysql2Adapter::Column.new("title", "Hello", Type::Text.new)
end
- text_column = Mysql2Adapter::Column.new("title", nil, "text")
+ text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)
assert_equal nil, text_column.default
- not_null_text_column = Mysql2Adapter::Column.new("title", nil, "text", false)
+ not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new, "text", false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
- blob_column = Mysql2Adapter::Column.new("title", nil, "blob")
+ blob_column = Mysql2Adapter::Column.new("title", nil, Type::Binary.new, "blob")
assert !blob_column.has_default?
- text_column = Mysql2Adapter::Column.new("title", nil, "text")
+ text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new)
assert !text_column.has_default?
end
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index 2a6d8cc2ab..3257f5bed8 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module ConnectionAdapters
class ColumnTest < ActiveRecord::TestCase
def test_type_cast_boolean
- column = Column.new("field", nil, "boolean")
+ column = Column.new("field", nil, Type::Boolean.new)
assert column.type_cast('').nil?
assert column.type_cast(nil).nil?
@@ -35,8 +35,15 @@ module ActiveRecord
assert_equal false, column.type_cast('SOMETHING RANDOM')
end
+ def test_type_cast_string
+ column = Column.new("field", nil, Type::String.new)
+ assert_equal "1", column.type_cast(true)
+ assert_equal "0", column.type_cast(false)
+ assert_equal "123", column.type_cast(123)
+ end
+
def test_type_cast_integer
- column = Column.new("field", nil, "integer")
+ column = Column.new("field", nil, Type::Integer.new)
assert_equal 1, column.type_cast(1)
assert_equal 1, column.type_cast('1')
assert_equal 1, column.type_cast('1ignore')
@@ -49,31 +56,50 @@ module ActiveRecord
end
def test_type_cast_non_integer_to_integer
- column = Column.new("field", nil, "integer")
+ column = Column.new("field", nil, Type::Integer.new)
assert_nil column.type_cast([1,2])
assert_nil column.type_cast({1 => 2})
assert_nil column.type_cast((1..2))
end
def test_type_cast_activerecord_to_integer
- column = Column.new("field", nil, "integer")
+ column = Column.new("field", nil, Type::Integer.new)
firm = Firm.create(:name => 'Apple')
assert_nil column.type_cast(firm)
end
def test_type_cast_object_without_to_i_to_integer
- column = Column.new("field", nil, "integer")
+ column = Column.new("field", nil, Type::Integer.new)
assert_nil column.type_cast(Object.new)
end
def test_type_cast_nan_and_infinity_to_integer
- column = Column.new("field", nil, "integer")
+ column = Column.new("field", nil, Type::Integer.new)
assert_nil column.type_cast(Float::NAN)
assert_nil column.type_cast(1.0/0.0)
end
+ def test_type_cast_float
+ column = Column.new("field", nil, Type::Float.new)
+ assert_equal 1.0, column.type_cast("1")
+ end
+
+ def test_type_cast_decimal
+ column = Column.new("field", nil, Type::Decimal.new)
+ assert_equal BigDecimal.new("0"), column.type_cast(BigDecimal.new("0"))
+ assert_equal BigDecimal.new("123"), column.type_cast(123.0)
+ assert_equal BigDecimal.new("1"), column.type_cast(:"1")
+ end
+
+ def test_type_cast_binary
+ column = Column.new("field", nil, Type::Binary.new)
+ assert_equal nil, column.type_cast(nil)
+ assert_equal "1", column.type_cast("1")
+ assert_equal 1, column.type_cast(1)
+ end
+
def test_type_cast_time
- column = Column.new("field", nil, "time")
+ column = Column.new("field", nil, Type::Time.new)
assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast('ABC')
@@ -83,19 +109,18 @@ module ActiveRecord
end
def test_type_cast_datetime_and_timestamp
- [Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column|
- assert_equal nil, column.type_cast(nil)
- assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast(' ')
- assert_equal nil, column.type_cast('ABC')
-
- datetime_string = Time.now.utc.strftime("%FT%T")
- assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
- end
+ column = Column.new("field", nil, Type::DateTime.new)
+ assert_equal nil, column.type_cast(nil)
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
end
def test_type_cast_date
- column = Column.new("field", nil, "date")
+ column = Column.new("field", nil, Type::Date.new)
assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast(' ')
@@ -106,7 +131,7 @@ module ActiveRecord
end
def test_type_cast_duration_to_integer
- column = Column.new("field", nil, "integer")
+ column = Column.new("field", nil, Type::Integer.new)
assert_equal 1800, column.type_cast(30.minutes)
assert_equal 7200, column.type_cast(2.hours)
end
@@ -114,10 +139,21 @@ module ActiveRecord
def test_string_to_time_with_timezone
[:utc, :local].each do |zone|
with_timezone_config default: zone do
- assert_equal Time.utc(2013, 9, 4, 0, 0, 0), Column.string_to_time("Wed, 04 Sep 2013 03:00:00 EAT")
+ column = Column.new("field", nil, Type::DateTime.new)
+ assert_equal Time.utc(2013, 9, 4, 0, 0, 0), column.type_cast("Wed, 04 Sep 2013 03:00:00 EAT")
end
end
end
+
+ if current_adapter?(:SQLite3Adapter)
+ def test_binary_encoding
+ column = Column.new("field", nil, SQLite3Binary.new)
+ utf8_string = "a string".encode(Encoding::UTF_8)
+ type_cast = column.type_cast(utf8_string)
+
+ assert_equal Encoding::ASCII_8BIT, type_cast.encoding
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
deleted file mode 100644
index deed226eab..0000000000
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- module ConnectionAdapters
- class ConnectionPool
- def insert_connection_for_test!(c)
- synchronize do
- @connections << c
- @available.add c
- end
- end
- end
-
- class AbstractAdapterTest < ActiveRecord::TestCase
- attr_reader :adapter
-
- def setup
- @adapter = AbstractAdapter.new nil, nil
- end
-
- def test_in_use?
- assert_not adapter.in_use?, 'adapter is not in use'
- assert adapter.lease, 'lease adapter'
- assert adapter.in_use?, 'adapter is in use'
- end
-
- def test_lease_twice
- assert adapter.lease, 'should lease adapter'
- assert_not adapter.lease, 'should not lease adapter'
- end
-
- def test_expire_mutates_in_use
- assert adapter.lease, 'lease adapter'
- assert adapter.in_use?, 'adapter is in use'
- adapter.expire
- assert_not adapter.in_use?, 'adapter is in use'
- end
-
- def test_close
- pool = ConnectionPool.new(ConnectionSpecification.new({}, nil))
- pool.insert_connection_for_test! adapter
- adapter.pool = pool
-
- # Make sure the pool marks the connection in use
- assert_equal adapter, pool.connection
- assert adapter.in_use?
-
- # Close should put the adapter back in the pool
- adapter.close
- assert_not adapter.in_use?
-
- assert_equal adapter, pool.connection
- end
- end
- end
-end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
new file mode 100644
index 0000000000..662e19f35e
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AdapterLeasingTest < ActiveRecord::TestCase
+ class Pool < ConnectionPool
+ def insert_connection_for_test!(c)
+ synchronize do
+ @connections << c
+ @available.add c
+ end
+ end
+ end
+
+ def setup
+ @adapter = AbstractAdapter.new nil, nil
+ end
+
+ def test_in_use?
+ assert_not @adapter.in_use?, 'adapter is not in use'
+ assert @adapter.lease, 'lease adapter'
+ assert @adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_lease_twice
+ assert @adapter.lease, 'should lease adapter'
+ assert_not @adapter.lease, 'should not lease adapter'
+ end
+
+ def test_expire_mutates_in_use
+ assert @adapter.lease, 'lease adapter'
+ assert @adapter.in_use?, 'adapter is in use'
+ @adapter.expire
+ assert_not @adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_close
+ pool = Pool.new(ConnectionSpecification.new({}, nil))
+ pool.insert_connection_for_test! @adapter
+ @adapter.pool = pool
+
+ # Make sure the pool marks the connection in use
+ assert_equal @adapter, pool.connection
+ assert @adapter.in_use?
+
+ # Close should put the adapter back in the pool
+ @adapter.close
+ assert_not @adapter.in_use?
+
+ assert_equal @adapter, pool.connection
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index e097449029..3e33b30144 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -2,180 +2,6 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
-
- class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase
-
- def klass
- ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig
- end
-
- def setup
- @previous_database_url = ENV.delete("DATABASE_URL")
- end
-
- teardown do
- ENV["DATABASE_URL"] = @previous_database_url
- end
-
- def resolve(spec, config)
- ConnectionSpecification::Resolver.new(klass.new(config).resolve).resolve(spec)
- end
-
- def spec(spec, config)
- ConnectionSpecification::Resolver.new(klass.new(config).resolve).spec(spec)
- end
-
- def test_resolver_with_database_uri_and_known_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = resolve(:production, config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_known_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = assert_deprecated { resolve("production", config) }
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_unknown_symbol_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = resolve(:production, config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_resolver_with_database_uri_and_unknown_string_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- assert_raises AdapterNotSpecified do
- spec("production", config)
- end
- end
-
- def test_resolver_with_database_uri_and_supplied_url
- ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo"
- config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
- actual = resolve("postgres://localhost/foo", config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expected, actual
- end
-
- def test_jdbc_url
- config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_environment_does_not_exist_in_config_url_does_exist
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = klass.new(config).resolve
- expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
- assert_equal expect_prod, actual["production"]
- end
-
- def test_string_connection
- config = { "production" => "postgres://localhost/foo" }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_url_sub_key
- config = { "production" => { "url" => "postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_hash
- config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_blank
- config = {}
- actual = klass.new(config).resolve
- assert_equal config, actual
- end
-
- def test_blank_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {}
- actual = klass.new(config).resolve
- expected = { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost" }
- assert_equal expected, actual["production"]
- assert_equal expected, actual["development"]
- assert_equal expected, actual["test"]
- assert_equal nil, actual[:production]
- assert_equal nil, actual[:development]
- assert_equal nil, actual[:test]
- end
-
- def test_url_sub_key_with_database_url
- ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
-
- config = { "production" => { "url" => "postgres://localhost/foo" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
- def test_merge_no_conflicts_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {"production" => { "pool" => "5" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost",
- "pool" => "5"
- }
- }
- assert_equal expected, actual
- end
-
- def test_merge_conflicts_with_database_url
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
-
- config = {"production" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
- actual = klass.new(config).resolve
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost",
- "pool" => "5"
- }
- }
- assert_equal expected, actual
- end
- end
-
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
@klass = Class.new(Base) { def self.name; 'klass'; end }
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
new file mode 100644
index 0000000000..da852aaa02
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -0,0 +1,192 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase
+ def setup
+ @previous_database_url = ENV.delete("DATABASE_URL")
+ end
+
+ teardown do
+ ENV["DATABASE_URL"] = @previous_database_url
+ end
+
+ def resolve_config(config)
+ ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
+ end
+
+ def resolve_spec(spec, config)
+ ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec)
+ end
+
+ def test_resolver_with_database_uri_and_current_env_symbol_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = resolve_spec(:default_env, config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_and_current_env_string_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = assert_deprecated { resolve_spec("default_env", config) }
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_known_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
+ actual = resolve_spec(:production, config)
+ expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_unknown_symbol_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ assert_raises AdapterNotSpecified do
+ resolve_spec(:production, config)
+ end
+ end
+
+ def test_resolver_with_database_uri_and_unknown_string_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ assert_deprecated do
+ assert_raises AdapterNotSpecified do
+ resolve_spec("production", config)
+ end
+ end
+ end
+
+ def test_resolver_with_database_uri_and_supplied_url
+ ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo"
+ config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
+ actual = resolve_spec("postgres://localhost/foo", config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_jdbc_url
+ config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_environment_does_not_exist_in_config_url_does_exist
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = resolve_config(config)
+ expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expect_prod, actual["default_env"]
+ end
+
+ def test_url_with_hyphenated_scheme
+ ENV['DATABASE_URL'] = "ibm-db://localhost/foo"
+ config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
+ actual = resolve_spec(:default_env, config)
+ expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_string_connection
+ config = { "default_env" => "postgres://localhost/foo" }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_url_sub_key
+ config = { "default_env" => { "url" => "postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_hash
+ config = { "production" => { "adapter" => "postgres", "database" => "foo" } }
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_blank
+ config = {}
+ actual = resolve_config(config)
+ assert_equal config, actual
+ end
+
+ def test_blank_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {}
+ actual = resolve_config(config)
+ expected = { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost" }
+ assert_equal expected, actual["default_env"]
+ assert_equal nil, actual["production"]
+ assert_equal nil, actual["development"]
+ assert_equal nil, actual["test"]
+ assert_equal nil, actual[:production]
+ assert_equal nil, actual[:development]
+ assert_equal nil, actual[:test]
+ end
+
+ def test_url_sub_key_with_database_url
+ ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
+
+ config = { "default_env" => { "url" => "postgres://localhost/foo" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_no_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"default_env" => { "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+
+ def test_merge_conflicts_with_database_url
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+
+ config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } }
+ actual = resolve_config(config)
+ expected = { "default_env" =>
+ { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => "5"
+ }
+ }
+ assert_equal expected, actual
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
new file mode 100644
index 0000000000..d4d67487db
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb
@@ -0,0 +1,61 @@
+require "cases/helper"
+
+if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+module ActiveRecord
+ module ConnectionAdapters
+ class MysqlTypeLookupTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_boolean_types
+ emulate_booleans(true) do
+ assert_lookup_type :boolean, 'tinyint(1)'
+ assert_lookup_type :boolean, 'TINYINT(1)'
+ end
+ end
+
+ def test_string_types
+ assert_lookup_type :string, "enum('one', 'two', 'three')"
+ assert_lookup_type :string, "ENUM('one', 'two', 'three')"
+ assert_lookup_type :string, "set('one', 'two', 'three')"
+ assert_lookup_type :string, "SET('one', 'two', 'three')"
+ end
+
+ def test_binary_types
+ assert_lookup_type :binary, 'bit'
+ assert_lookup_type :binary, 'BIT'
+ end
+
+ def test_integer_types
+ emulate_booleans(false) do
+ assert_lookup_type :integer, 'tinyint(1)'
+ assert_lookup_type :integer, 'TINYINT(1)'
+ assert_lookup_type :integer, 'year'
+ assert_lookup_type :integer, 'YEAR'
+ end
+ end
+
+ private
+
+ def assert_lookup_type(type, lookup)
+ cast_type = @connection.type_map.lookup(lookup)
+ assert_equal type, cast_type.type
+ end
+
+ def emulate_booleans(value)
+ old_emulate_booleans = @connection.emulate_booleans
+ change_emulate_booleans(value)
+ yield
+ ensure
+ change_emulate_booleans(old_emulate_booleans)
+ end
+
+ def change_emulate_booleans(value)
+ @connection.emulate_booleans = value
+ @connection.clear_cache!
+ end
+ end
+ end
+end
+end
diff --git a/activerecord/test/cases/connection_adapters/type/type_map_test.rb b/activerecord/test/cases/connection_adapters/type/type_map_test.rb
new file mode 100644
index 0000000000..4b4d9f6b0f
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/type/type_map_test.rb
@@ -0,0 +1,115 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ module Type
+ class TypeMapTest < ActiveRecord::TestCase
+ def test_default_type
+ mapping = TypeMap.new
+
+ assert_kind_of Value, mapping.lookup(:undefined)
+ end
+
+ def test_registering_types
+ boolean = Boolean.new
+ mapping = TypeMap.new
+
+ mapping.register_type(/boolean/i, boolean)
+
+ assert_equal mapping.lookup('boolean'), boolean
+ end
+
+ def test_overriding_registered_types
+ time = Time.new
+ timestamp = DateTime.new
+ mapping = TypeMap.new
+
+ mapping.register_type(/time/i, time)
+ mapping.register_type(/time/i, timestamp)
+
+ assert_equal mapping.lookup('time'), timestamp
+ end
+
+ def test_fuzzy_lookup
+ string = String.new
+ mapping = TypeMap.new
+
+ mapping.register_type(/varchar/i, string)
+
+ assert_equal mapping.lookup('varchar(20)'), string
+ end
+
+ def test_aliasing_types
+ string = String.new
+ mapping = TypeMap.new
+
+ mapping.register_type(/string/i, string)
+ mapping.alias_type(/varchar/i, 'string')
+
+ assert_equal mapping.lookup('varchar'), string
+ end
+
+ def test_changing_type_changes_aliases
+ time = Time.new
+ timestamp = DateTime.new
+ mapping = TypeMap.new
+
+ mapping.register_type(/timestamp/i, time)
+ mapping.alias_type(/datetime/i, 'timestamp')
+ mapping.register_type(/timestamp/i, timestamp)
+
+ assert_equal mapping.lookup('datetime'), timestamp
+ end
+
+ def test_aliases_keep_metadata
+ mapping = TypeMap.new
+
+ mapping.register_type(/decimal/i) { |sql_type| sql_type }
+ mapping.alias_type(/number/i, 'decimal')
+
+ assert_equal mapping.lookup('number(20)'), 'decimal(20)'
+ assert_equal mapping.lookup('number'), 'decimal'
+ end
+
+ def test_register_proc
+ string = String.new
+ binary = Binary.new
+ mapping = TypeMap.new
+
+ mapping.register_type(/varchar/i) do |type|
+ if type.include?('(')
+ string
+ else
+ binary
+ end
+ end
+
+ assert_equal mapping.lookup('varchar(20)'), string
+ assert_equal mapping.lookup('varchar'), binary
+ end
+
+ def test_requires_value_or_block
+ mapping = TypeMap.new
+
+ assert_raises(ArgumentError) do
+ mapping.register_type(/only key/i)
+ end
+ end
+
+ def test_lookup_non_strings
+ mapping = HashLookupTypeMap.new
+
+ mapping.register_type(1, 'string')
+ mapping.register_type(2, 'int')
+ mapping.alias_type(3, 1)
+
+ assert_equal mapping.lookup(1), 'string'
+ assert_equal mapping.lookup(2), 'int'
+ assert_equal mapping.lookup(3), 'string'
+ assert_kind_of Type::Value, mapping.lookup(4)
+ end
+ end
+ end
+ end
+end
+
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
new file mode 100644
index 0000000000..18df30faf5
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -0,0 +1,97 @@
+require "cases/helper"
+
+unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strigns for lookup
+module ActiveRecord
+ module ConnectionAdapters
+ class TypeLookupTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_boolean_types
+ assert_lookup_type :boolean, 'boolean'
+ assert_lookup_type :boolean, 'BOOLEAN'
+ end
+
+ def test_string_types
+ assert_lookup_type :string, 'char'
+ assert_lookup_type :string, 'varchar'
+ assert_lookup_type :string, 'VARCHAR'
+ assert_lookup_type :string, 'varchar(255)'
+ assert_lookup_type :string, 'character varying'
+ end
+
+ def test_binary_types
+ assert_lookup_type :binary, 'binary'
+ assert_lookup_type :binary, 'BINARY'
+ assert_lookup_type :binary, 'blob'
+ assert_lookup_type :binary, 'BLOB'
+ end
+
+ def test_text_types
+ assert_lookup_type :text, 'text'
+ assert_lookup_type :text, 'TEXT'
+ assert_lookup_type :text, 'clob'
+ assert_lookup_type :text, 'CLOB'
+ end
+
+ def test_date_types
+ assert_lookup_type :date, 'date'
+ assert_lookup_type :date, 'DATE'
+ end
+
+ def test_time_types
+ assert_lookup_type :time, 'time'
+ assert_lookup_type :time, 'TIME'
+ end
+
+ def test_datetime_types
+ assert_lookup_type :datetime, 'datetime'
+ assert_lookup_type :datetime, 'DATETIME'
+ assert_lookup_type :datetime, 'timestamp'
+ assert_lookup_type :datetime, 'TIMESTAMP'
+ end
+
+ def test_decimal_types
+ assert_lookup_type :decimal, 'decimal'
+ assert_lookup_type :decimal, 'decimal(2,8)'
+ assert_lookup_type :decimal, 'DECIMAL'
+ assert_lookup_type :decimal, 'numeric'
+ assert_lookup_type :decimal, 'numeric(2,8)'
+ assert_lookup_type :decimal, 'NUMERIC'
+ assert_lookup_type :decimal, 'number'
+ assert_lookup_type :decimal, 'number(2,8)'
+ assert_lookup_type :decimal, 'NUMBER'
+ end
+
+ def test_float_types
+ assert_lookup_type :float, 'float'
+ assert_lookup_type :float, 'FLOAT'
+ assert_lookup_type :float, 'double'
+ assert_lookup_type :float, 'DOUBLE'
+ end
+
+ def test_integer_types
+ assert_lookup_type :integer, 'integer'
+ assert_lookup_type :integer, 'INTEGER'
+ assert_lookup_type :integer, 'tinyint'
+ assert_lookup_type :integer, 'smallint'
+ assert_lookup_type :integer, 'bigint'
+ assert_lookup_type :integer, 'decimal(2)'
+ assert_lookup_type :integer, 'decimal(2,0)'
+ assert_lookup_type :integer, 'numeric(2)'
+ assert_lookup_type :integer, 'numeric(2,0)'
+ assert_lookup_type :integer, 'number(2)'
+ assert_lookup_type :integer, 'number(2,0)'
+ end
+
+ private
+
+ def assert_lookup_type(type, lookup)
+ cast_type = @connection.type_map.lookup(lookup)
+ assert_equal type, cast_type.type
+ end
+ end
+ end
+end
+end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index ee3d8a81c2..ab2a749ba8 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -51,6 +51,16 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test "reset counters by counter name" do
+ # throw the count off by 1
+ Topic.increment_counter(:replies_count, @topic.id)
+
+ # check that it gets reset
+ assert_difference '@topic.reload.replies_count', -1 do
+ Topic.reset_counters(@topic.id, :replies_count)
+ end
+ end
+
test 'reset multiple counters' do
Topic.update_counters @topic.id, replies_count: 1, unique_replies_count: 1
assert_difference ['@topic.reload.replies_count', '@topic.reload.unique_replies_count'], -1 do
@@ -154,10 +164,10 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
- test "the passed symbol needs to be an association name" do
+ test "the passed symbol needs to be an association name or counter name" do
e = assert_raises(ArgumentError) do
- Topic.reset_counters(@topic.id, :replies_count)
+ Topic.reset_counters(@topic.id, :undefined_count)
end
- assert_equal "'Topic' has no association called 'replies_count'", e.message
+ assert_equal "'Topic' has no association called 'undefined_count'", e.message
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 7d438803a1..f885a8cbc0 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -18,7 +18,7 @@ class DefaultTest < ActiveRecord::TestCase
end
end
- if current_adapter?(:PostgreSQLAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
def test_default_integers
default = Default.new
assert_instance_of Fixnum, default.positive_integer
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index 1e6ccecfab..409d9a82e2 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/reply'
require 'models/topic'
module ActiveRecord
@@ -32,6 +33,14 @@ module ActiveRecord
assert duped.new_record?, 'topic is new'
end
+ def test_dup_not_destroyed
+ topic = Topic.first
+ topic.destroy
+
+ duped = topic.dup
+ assert_not duped.destroyed?
+ end
+
def test_dup_has_no_id
topic = Topic.first
duped = topic.dup
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 47de3dec98..3b2f0dfe07 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -250,4 +250,40 @@ class EnumTest < ActiveRecord::TestCase
valid_book = klass.new(status: "written")
assert valid_book.valid?
end
+
+ test "enums are distinct per class" do
+ klass1 = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:proposed, :written]
+ end
+
+ klass2 = Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum status: [:drafted, :uploaded]
+ end
+
+ book1 = klass1.proposed.create!
+ book1.status = :written
+ assert_equal ['proposed', 'written'], book1.status_change
+
+ book2 = klass2.drafted.create!
+ book2.status = :uploaded
+ assert_equal ['drafted', 'uploaded'], book2.status_change
+ end
+
+ test "enums are inheritable" do
+ subklass1 = Class.new(Book)
+
+ subklass2 = Class.new(Book) do
+ enum status: [:drafted, :uploaded]
+ end
+
+ book1 = subklass1.proposed.create!
+ book1.status = :written
+ assert_equal ['proposed', 'written'], book1.status_change
+
+ book2 = subklass2.drafted.create!
+ book2.status = :uploaded
+ assert_equal ['drafted', 'uploaded'], book2.status_change
+ end
end
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 6dac5db111..9d25bdd82a 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -26,8 +26,12 @@ if ActiveRecord::Base.connection.supports_explain?
sql, binds = queries[0]
assert_match "SELECT", sql
- assert_match "honda", sql
- assert_equal [], binds
+ if binds.any?
+ assert_equal 1, binds.length
+ assert_equal "honda", binds.flatten.last
+ else
+ assert_match 'honda', sql
+ end
end
def test_exec_explain_with_no_binds
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index cf0235b8c5..8bbc0af758 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -677,6 +677,12 @@ end
class FoxyFixturesTest < ActiveRecord::TestCase
fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users"
+ if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
+ require 'models/uuid_parent'
+ require 'models/uuid_child'
+ fixtures :uuid_parents, :uuid_children
+ end
+
def test_identifies_strings
assert_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("foo"))
assert_not_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("FOO"))
@@ -689,6 +695,9 @@ class FoxyFixturesTest < ActiveRecord::TestCase
def test_identifies_consistently
assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby)
assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2)
+
+ assert_equal 'f92b6bda-0d0d-5fe1-9124-502b18badded', ActiveRecord::FixtureSet.identify(:daddy, :uuid)
+ assert_equal 'b4b10018-ad47-595d-b42f-d8bdaa6d01bf', ActiveRecord::FixtureSet.identify(:sonny, :uuid)
end
TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 5ed508a799..eaf2cada9d 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -111,6 +111,15 @@ def verify_default_timezone_config
end
end
+def enable_uuid_ossp!(connection)
+ return false unless connection.supports_extensions?
+ return true if connection.extension_enabled?('uuid-ossp')
+
+ connection.enable_extension 'uuid-ossp'
+ connection.commit_db_transaction
+ connection.reconnect!
+end
+
unless ENV['FIXTURE_DEBUG']
module ActiveRecord::TestFixtures::ClassMethods
def try_to_load_dependency_with_silence(*args)
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index 367d04a154..b4617cf6f9 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -15,7 +15,7 @@ class HotCompatibilityTest < ActiveRecord::TestCase
end
teardown do
- @klass.connection.drop_table :hot_compatibilities
+ ActiveRecord::Base.connection.drop_table :hot_compatibilities
end
test "insert after remove_column" do
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index f5f85f2412..792950d24d 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -95,16 +95,8 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_a_bad_type_column
- #SQLServer need to turn Identity Insert On before manually inserting into the Identity column
- if current_adapter?(:SybaseAdapter)
- Company.connection.execute "SET IDENTITY_INSERT companies ON"
- end
Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
- #We then need to turn it back Off before continuing.
- if current_adapter?(:SybaseAdapter)
- Company.connection.execute "SET IDENTITY_INSERT companies OFF"
- end
assert_raise(ActiveRecord::SubclassNotFound) { Company.find(100) }
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index c373dc1511..93fd3b9605 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -339,8 +339,6 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
def add_counter_column_to(model, col='test_count')
model.connection.add_column model.table_name, col, :integer, :null => false, :default => 0
model.reset_column_information
- # OpenBase does not set a value to existing rows when adding a not null default column
- model.update_all(col => 0) if current_adapter?(:OpenBaseAdapter)
end
def remove_counter_column_from(model, col = :test_count)
@@ -367,7 +365,7 @@ end
# is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
# blocks, so separate script called by Kernel#system is needed.
# (See exec vs. async_exec in the PostgreSQL adapter.)
-unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
+unless in_memory_db?
class PessimisticLockingTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
fixtures :people, :readers
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 97c0350911..a578e81844 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -125,5 +125,12 @@ class LogSubscriberTest < ActiveRecord::TestCase
wait
assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join)
end
+
+ def test_nil_binary_data_is_logged
+ binary = Binary.create(data: "")
+ binary.update_attributes(data: nil)
+ wait
+ assert_match(/<NULL binary data>/, @logger.logged(:debug).join)
+ end
end
end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 5418d913b0..9b26c30d14 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -14,6 +14,7 @@ module ActiveRecord
teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
+ ActiveRecord::Base.clear_cache!
end
def test_create_table_without_id
@@ -204,9 +205,9 @@ module ActiveRecord
connection.create_table table_name
end
- # Sybase, and SQLite3 will not allow you to add a NOT NULL
+ # SQLite3 will not allow you to add a NOT NULL
# column to a table without a default value.
- unless current_adapter?(:SybaseAdapter, :SQLite3Adapter)
+ unless current_adapter?(:SQLite3Adapter)
def test_add_column_not_null_without_default
connection.create_table :testings do |t|
t.column :foo, :string
@@ -225,18 +226,28 @@ module ActiveRecord
end
con = connection
- connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
- connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
assert_nothing_raised {connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
assert_raises(ActiveRecord::StatementInvalid) do
- unless current_adapter?(:OpenBaseAdapter)
- connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
- else
- connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)",
- "Testing Insert","id",2)
- end
+ connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
+ end
+ end
+
+ def test_add_column_with_timestamp_type
+ connection.create_table :testings do |t|
+ t.column :foo, :timestamp
+ end
+
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = 'testings'
+
+ assert_equal :datetime, klass.columns_hash['foo'].type
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'timestamp without time zone', klass.columns_hash['foo'].sql_type
+ else
+ assert_equal klass.connection.type_to_sql('datetime'), klass.columns_hash['foo'].sql_type
end
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 6a02873cba..984d1c2597 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -62,7 +62,7 @@ module ActiveRecord
# Do a manual insertion
if current_adapter?(:OracleAdapter)
connection.execute "insert into test_models (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
- elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
+ elsif current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003 #before mysql 5.0.3 decimals stored as strings
connection.execute "insert into test_models (wealth) values ('12345678901234567890.0123456789')"
elsif current_adapter?(:PostgreSQLAdapter)
connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)"
@@ -160,8 +160,8 @@ module ActiveRecord
assert_equal Fixnum, bob.age.class
assert_equal Time, bob.birthday.class
- if current_adapter?(:OracleAdapter, :SybaseAdapter)
- # Sybase, and Oracle don't differentiate between date/time
+ if current_adapter?(:OracleAdapter)
+ # Oracle doesn't differentiate between date/time
assert_equal Time, bob.favorite_day.class
else
assert_equal Date, bob.favorite_day.class
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 2d7a7ec73a..a7c287515d 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -274,6 +274,16 @@ module ActiveRecord
ensure
connection.drop_table(:my_table) rescue nil
end
+
+ def test_column_with_index
+ connection.create_table "my_table", force: true do |t|
+ t.string :item_number, index: true
+ end
+
+ assert connection.index_exists?("my_table", :item_number, name: :index_my_table_on_item_number)
+ ensure
+ connection.drop_table(:my_table) rescue nil
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 35af11f672..93c3bfae7a 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -26,28 +26,25 @@ module ActiveRecord
ActiveRecord::Base.primary_key_prefix_type = nil
end
- unless current_adapter?(:OpenBaseAdapter)
- def test_rename_index
- # keep the names short to make Oracle and similar behave
- connection.add_index(table_name, [:foo], :name => 'old_idx')
- connection.rename_index(table_name, 'old_idx', 'new_idx')
-
- # if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert_not connection.index_name_exists?(table_name, 'old_idx', false)
- assert connection.index_name_exists?(table_name, 'new_idx', true)
- end
+ def test_rename_index
+ # keep the names short to make Oracle and similar behave
+ connection.add_index(table_name, [:foo], :name => 'old_idx')
+ connection.rename_index(table_name, 'old_idx', 'new_idx')
+
+ # if the adapter doesn't support the indexes call, pick defaults that let the test pass
+ assert_not connection.index_name_exists?(table_name, 'old_idx', false)
+ assert connection.index_name_exists?(table_name, 'new_idx', true)
+ end
- def test_double_add_index
+ def test_double_add_index
+ connection.add_index(table_name, [:foo], :name => 'some_idx')
+ assert_raises(ArgumentError) {
connection.add_index(table_name, [:foo], :name => 'some_idx')
- assert_raises(ArgumentError) {
- connection.add_index(table_name, [:foo], :name => 'some_idx')
- }
- end
+ }
+ end
- def test_remove_nonexistent_index
- # we do this by name, so OpenBase is a wash as noted above
- assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") }
- end
+ def test_remove_nonexistent_index
+ assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") }
end
def test_add_index_works_with_long_index_names
@@ -126,50 +123,37 @@ module ActiveRecord
connection.add_index("testings", "last_name")
connection.remove_index("testings", "last_name")
- # Orcl nds shrt indx nms. Sybs 2.
- # OpenBase does not have named indexes. You must specify a single column name
- unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", :column => ["last_name", "first_name"])
+
+ # Oracle adapter cannot have specified index name larger than 30 characters
+ # Oracle adapter is shortening index name when just column list is given
+ unless current_adapter?(:OracleAdapter)
connection.add_index("testings", ["last_name", "first_name"])
- connection.remove_index("testings", :column => ["last_name", "first_name"])
-
- # Oracle adapter cannot have specified index name larger than 30 characters
- # Oracle adapter is shortening index name when just column list is given
- unless current_adapter?(:OracleAdapter)
- connection.add_index("testings", ["last_name", "first_name"])
- connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name)
- connection.add_index("testings", ["last_name", "first_name"])
- connection.remove_index("testings", "last_name_and_first_name")
- end
+ connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name)
connection.add_index("testings", ["last_name", "first_name"])
- connection.remove_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", "last_name_and_first_name")
+ end
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", ["last_name", "first_name"])
- connection.add_index("testings", ["last_name"], :length => 10)
- connection.remove_index("testings", "last_name")
+ connection.add_index("testings", ["last_name"], :length => 10)
+ connection.remove_index("testings", "last_name")
- connection.add_index("testings", ["last_name"], :length => {:last_name => 10})
- connection.remove_index("testings", ["last_name"])
+ connection.add_index("testings", ["last_name"], :length => {:last_name => 10})
+ connection.remove_index("testings", ["last_name"])
- connection.add_index("testings", ["last_name", "first_name"], :length => 10)
- connection.remove_index("testings", ["last_name", "first_name"])
+ connection.add_index("testings", ["last_name", "first_name"], :length => 10)
+ connection.remove_index("testings", ["last_name", "first_name"])
- connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20})
- connection.remove_index("testings", ["last_name", "first_name"])
- end
+ connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20})
+ connection.remove_index("testings", ["last_name", "first_name"])
- # quoting
- # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
- # OpenBase does not have named indexes. You must specify a single column name
- unless current_adapter?(:OpenBaseAdapter)
- connection.add_index("testings", ["key"], :name => "key_idx", :unique => true)
- connection.remove_index("testings", :name => "key_idx", :unique => true)
- end
+ connection.add_index("testings", ["key"], :name => "key_idx", :unique => true)
+ connection.remove_index("testings", :name => "key_idx", :unique => true)
- # Sybase adapter does not support indexes on :boolean columns
- # OpenBase does not have named indexes. You must specify a single column
- unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
- connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin")
- connection.remove_index("testings", :name => "named_admin")
- end
+ connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin")
+ connection.remove_index("testings", :name => "named_admin")
# Selected adapters support index sort order
if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 2a7fafc559..e0b03f4735 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -42,13 +42,8 @@ module ActiveRecord
def test_rename_table
rename_table :test_models, :octopi
- # Using explicit id in insert for compatibility across all databases
- connection.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
-
connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
- connection.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
-
assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
end
@@ -57,10 +52,7 @@ module ActiveRecord
rename_table :test_models, :octopi
- # Using explicit id in insert for compatibility across all databases
- connection.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
connection.execute "INSERT INTO octopi (#{connection.quote_column_name('id')}, #{connection.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
- connection.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
index = connection.indexes(:octopi).first
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index c77a818b93..9568aa2217 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -92,7 +92,7 @@ module ActiveRecord
def test_relative_migrations
list = Dir.chdir(MIGRATIONS_ROOT) do
- ActiveRecord::Migrator.migrations("valid/")
+ ActiveRecord::Migrator.migrations("valid")
end
migration_proxy = list.find { |item|
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index f7db195521..e87773df94 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -112,6 +112,34 @@ class ModulesTest < ActiveRecord::TestCase
classes.each(&:reset_table_name)
end
+ def test_module_table_name_suffix
+ assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_suffix'
+ assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_suffix'
+ assert_equal 'companies', MyApplication::Business::Suffixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed'
+ end
+
+ def test_module_table_name_suffix_with_global_suffix
+ classes = [ MyApplication::Business::Company,
+ MyApplication::Business::Firm,
+ MyApplication::Business::Client,
+ MyApplication::Business::Client::Contact,
+ MyApplication::Business::Developer,
+ MyApplication::Business::Project,
+ MyApplication::Business::Suffixed::Company,
+ MyApplication::Business::Suffixed::Nested::Company,
+ MyApplication::Billing::Account ]
+
+ ActiveRecord::Base.table_name_suffix = '_global'
+ classes.each(&:reset_table_name)
+ assert_equal 'companies_global', MyApplication::Business::Company.table_name, 'inferred table_name for ActiveRecord model in module without table_name_suffix'
+ assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Company.table_name, 'inferred table_name for ActiveRecord model in module with table_name_suffix'
+ assert_equal 'companies_suffixed', MyApplication::Business::Suffixed::Nested::Company.table_name, 'table_name for ActiveRecord model in nested module with a parent table_name_suffix'
+ assert_equal 'companies', MyApplication::Business::Suffixed::Firm.table_name, 'explicit table_name for ActiveRecord model in module with table_name_suffix should not be suffixed'
+ ensure
+ ActiveRecord::Base.table_name_suffix = ''
+ classes.each(&:reset_table_name)
+ end
+
def test_compute_type_can_infer_class_name_of_sibling_inside_module
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index c70a8f296f..14d4ef457d 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -240,8 +240,8 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
Topic.skip_time_zone_conversion_for_attributes = []
end
- # Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter)
+ # Oracle does not have a TIME datatype.
+ unless current_adapter?(:OracleAdapter)
def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
attributes = {
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 046fe83e54..bc5ccd0fe9 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/aircraft'
require 'models/post'
require 'models/comment'
require 'models/author'
@@ -233,6 +234,15 @@ class PersistenceTest < ActiveRecord::TestCase
assert_nothing_raised { Minimalistic.create!(:id => 2) }
end
+ def test_save_with_duping_of_destroyed_object
+ developer = Developer.first
+ developer.destroy
+ new_developer = developer.dup
+ new_developer.save
+ assert new_developer.persisted?
+ assert_not new_developer.destroyed?
+ end
+
def test_create_many
topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
assert_equal 2, topics.size
@@ -820,4 +830,28 @@ class PersistenceTest < ActiveRecord::TestCase
end
end
+ def test_persist_inherited_class_with_different_table_name
+ minimalistic_aircrafts = Class.new(Minimalistic) do
+ self.table_name = "aircraft"
+ end
+
+ assert_difference "Aircraft.count", 1 do
+ aircraft = minimalistic_aircrafts.create(name: "Wright Flyer")
+ aircraft.name = "Wright Glider"
+ aircraft.save
+ end
+
+ assert_equal "Wright Glider", Aircraft.last.name
+ end
+
+ def test_instantiate_creates_a_new_instance
+ post = Post.instantiate("title" => "appropriate documentation", "type" => "SpecialPost")
+ assert_equal "appropriate documentation", post.title
+ assert_instance_of SpecialPost, post
+
+ # body was not initialized
+ assert_raises ActiveModel::MissingAttributeError do
+ post.body
+ end
+ end
end
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index dd0e934ec2..8eea10143f 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -48,4 +48,4 @@ class PooledConnectionsTest < ActiveRecord::TestCase
def add_record(name)
ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name }
end
-end unless current_adapter?(:FrontBase) || in_memory_db?
+end unless in_memory_db?
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 51ddd406ed..56d0dd6a77 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -219,3 +219,29 @@ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
end
end
end
+
+if current_adapter?(:PostgreSQLAdapter)
+ class PrimaryKeyBigSerialTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class Widget < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:widgets, id: :bigserial) { |t| }
+ end
+
+ teardown do
+ @connection.drop_table :widgets
+ end
+
+ def test_bigserial_primary_key
+ assert_equal "id", Widget.primary_key
+ assert_equal :integer, Widget.columns_hash[Widget.primary_key].type
+
+ widget = Widget.create!
+ assert_not_nil widget.id
+ end
+ end
+end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index ad77472333..c085fcf161 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -87,6 +87,14 @@ class ReflectionTest < ActiveRecord::TestCase
end
end
+ def test_irregular_reflection_class_name
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'plural_irregular', 'plurales_irregulares'
+ end
+ reflection = AssociationReflection.new(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base)
+ assert_equal 'PluralIrregular', reflection.class_name
+ end
+
def test_aggregation_reflection
reflection_for_address = AggregateReflection.new(
:composed_of, :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
@@ -192,7 +200,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_should_not_raise_error_when_compared_to_other_object
- assert_nothing_raised { Firm.reflections[:clients] == Object.new }
+ assert_nothing_raised { Firm.reflections['clients'] == Object.new }
end
def test_has_many_through_reflection
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index 9b2bfed039..29c9d0e2af 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -32,7 +32,7 @@ module ActiveRecord
:exclude?, :find_all, :flat_map, :group_by, :include?, :length,
:map, :none?, :one?, :partition, :reject, :reverse,
:sample, :second, :sort, :sort_by, :third,
- :to_ary, :to_set, :to_xml, :to_yaml
+ :to_ary, :to_set, :to_xml, :to_yaml, :join
]
ARRAY_DELEGATES.each do |method|
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 23500bf5d8..2b5c2fd5a4 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -17,10 +17,9 @@ class RelationMergingTest < ActiveRecord::TestCase
end
def test_relation_to_sql
- sql = Post.connection.unprepared_statement do
- Post.first.comments.to_sql
- end
- assert_no_match(/\?/, sql)
+ post = Post.first
+ sql = post.comments.to_sql
+ assert_match(/.?post_id.? = #{post.id}\Z/i, sql)
end
def test_relation_merging_with_arel_equalities_keeps_last_equality
@@ -81,31 +80,20 @@ class RelationMergingTest < ActiveRecord::TestCase
left = Post.where(title: "omg").where(comments_count: 1)
right = Post.where(title: "wtf").where(title: "bbq")
- expected = [left.where_values[1]] + right.where_values
+ expected = [left.bind_values[1]] + right.bind_values
merged = left.merge(right)
- assert_equal expected, merged.where_values
+ assert_equal expected, merged.bind_values
assert !merged.to_sql.include?("omg")
assert merged.to_sql.include?("wtf")
assert merged.to_sql.include?("bbq")
end
- def test_merging_removes_rhs_bind_parameters
- left = Post.where(id: Arel::Nodes::BindParam.new('?'))
- column = Post.columns_hash['id']
- left.bind_values += [[column, 20]]
- right = Post.where(id: 10)
-
- merged = left.merge(right)
- assert_equal [], merged.bind_values
- end
-
def test_merging_keeps_lhs_bind_parameters
column = Post.columns_hash['id']
binds = [[column, 20]]
- right = Post.where(id: Arel::Nodes::BindParam.new('?'))
- right.bind_values += binds
+ right = Post.where(id: 20)
left = Post.where(id: 10)
merged = left.merge(right)
@@ -113,21 +101,18 @@ class RelationMergingTest < ActiveRecord::TestCase
end
def test_merging_reorders_bind_params
- post = Post.first
- id_column = Post.columns_hash['id']
- title_column = Post.columns_hash['title']
-
- bv = Post.connection.substitute_at id_column, 0
-
- right = Post.where(id: bv)
- right.bind_values += [[id_column, post.id]]
-
- left = Post.where(title: bv)
- left.bind_values += [[title_column, post.title]]
+ post = Post.first
+ right = Post.where(id: 1)
+ left = Post.where(title: post.title)
merged = left.merge(right)
assert_equal post, merged.first
end
+
+ def test_merging_compares_symbols_and_strings_as_equal
+ post = PostThatLoadsCommentsInAnAfterSaveHook.create!(title: "First Post", body: "Blah blah blah.")
+ assert_equal "First comment!", post.comments.where(body: "First comment!").first_or_create.body
+ end
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 4fafa668fb..1da5c36e1c 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -24,13 +24,18 @@ module ActiveRecord
@relation ||= Relation.new FakeKlass.new('posts'), Post.arel_table
end
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope]).each do |method|
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order, :unscope, :select]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal [:foo], relation.public_send("#{method}_values")
end
end
+ test "#_select!" do
+ assert relation.public_send("_select!", :foo).equal?(relation)
+ assert_equal [:foo], relation.public_send("select_values")
+ end
+
test '#order!' do
assert relation.order!('name ASC').equal?(relation)
assert_equal ['name ASC'], relation.order_values
@@ -107,10 +112,18 @@ module ActiveRecord
end
test 'reverse_order!' do
- assert relation.reverse_order!.equal?(relation)
- assert relation.reverse_order_value
+ relation = Post.order('title ASC, comments_count DESC')
+
+ relation.reverse_order!
+
+ assert_equal 'title DESC', relation.order_values.first
+ assert_equal 'comments_count ASC', relation.order_values.last
+
+
relation.reverse_order!
- assert !relation.reverse_order_value
+
+ assert_equal 'title ASC', relation.order_values.first
+ assert_equal 'comments_count DESC', relation.order_values.last
end
test 'create_with!' do
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index c628ca44ff..c6decaad89 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -12,9 +12,15 @@ module ActiveRecord
end
def test_not_eq
- expected = Post.arel_table[@name].not_eq('hello')
relation = Post.where.not(title: 'hello')
- assert_equal([expected], relation.where_values)
+
+ assert_equal 1, relation.where_values.length
+
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'hello', bind.last
end
def test_not_null
@@ -44,21 +50,29 @@ module ActiveRecord
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
- expected = Post.arel_table[@name].eq('hello')
- assert_equal(expected, relation.where_values.first)
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
+ assert_equal 'hello', bind.last
- expected = Post.arel_table[@name].not_eq('world')
- assert_equal(expected, relation.where_values.last)
+ value = relation.where_values.last
+ bind = relation.bind_values.last
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'world', bind.last
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
- expected = Post.arel_table[@name].not_eq('hello')
- assert_equal(expected, relation.where_values.first)
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'hello', bind.last
- expected = Post.arel_table[@name].eq('world')
- assert_equal(expected, relation.where_values.last)
+ value = relation.where_values.last
+ bind = relation.bind_values.last
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
+ assert_equal 'world', bind.last
end
def test_not_eq_with_string_parameter
@@ -79,38 +93,61 @@ module ActiveRecord
expected = Post.arel_table['author_id'].not_in([1, 2])
assert_equal(expected, relation.where_values[0])
- expected = Post.arel_table[@name].not_eq('ruby on rails')
- assert_equal(expected, relation.where_values[1])
+ value = relation.where_values[1]
+ bind = relation.bind_values.first
+
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::NotEqual
+ assert_equal 'ruby on rails', bind.last
end
def test_rewhere_with_one_condition
relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone')
- expected = Post.arel_table[@name].eq('alone')
assert_equal 1, relation.where_values.size
- assert_equal expected, relation.where_values.first
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table[@name], Arel::Nodes::Equality
+ assert_equal 'alone', bind.last
end
def test_rewhere_with_multiple_overwriting_conditions
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again')
- title_expected = Post.arel_table['title'].eq('alone')
- body_expected = Post.arel_table['body'].eq('again')
-
assert_equal 2, relation.where_values.size
- assert_equal title_expected, relation.where_values.first
- assert_equal body_expected, relation.where_values.second
+
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+ assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
+ assert_equal 'alone', bind.last
+
+ value = relation.where_values[1]
+ bind = relation.bind_values[1]
+ assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
+ assert_equal 'again', bind.last
+ end
+
+ def assert_bound_ast value, table, type
+ assert_equal table, value.left
+ assert_kind_of type, value
+ assert_kind_of Arel::Nodes::BindParam, value.right
end
def test_rewhere_with_one_overwriting_condition_and_one_unrelated
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone')
- title_expected = Post.arel_table['title'].eq('alone')
- body_expected = Post.arel_table['body'].eq('world')
-
assert_equal 2, relation.where_values.size
- assert_equal body_expected, relation.where_values.first
- assert_equal title_expected, relation.where_values.second
+
+ value = relation.where_values.first
+ bind = relation.bind_values.first
+
+ assert_bound_ast value, Post.arel_table['body'], Arel::Nodes::Equality
+ assert_equal 'world', bind.last
+
+ value = relation.where_values.second
+ bind = relation.bind_values.second
+
+ assert_bound_ast value, Post.arel_table['title'], Arel::Nodes::Equality
+ assert_equal 'alone', bind.last
end
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 15611656fd..fb0b906c07 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -16,6 +16,10 @@ module ActiveRecord
def self.connection
Post.connection
end
+
+ def self.table_name
+ 'fake_table'
+ end
end
def test_construction
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 049c5a0606..fbba554e39 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -14,6 +14,7 @@ require 'models/car'
require 'models/engine'
require 'models/tyre'
require 'models/minivan'
+require 'models/aircraft'
class RelationTest < ActiveRecord::TestCase
@@ -365,6 +366,16 @@ class RelationTest < ActiveRecord::TestCase
assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)
end
+
+ def test_null_relation_count
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:id).count
+ assert_equal 0, ac.engines.count
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:id).count
+ assert_equal 0, ac.engines.count
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -497,6 +508,13 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_deep_preload
+ post = Post.preload(author: :posts, comments: :post).first
+
+ assert_predicate post.author.association(:posts), :loaded?
+ assert_predicate post.comments.first.association(:post), :loaded?
+ end
+
def test_preload_applies_to_all_chained_preloaded_scopes
assert_queries(3) do
post = Post.with_comments.with_tags.first
@@ -820,8 +838,22 @@ class RelationTest < ActiveRecord::TestCase
assert davids.loaded?
end
- def test_delete_all_limit_error
+ def test_delete_all_with_unpermitted_relation_raises_error
assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.uniq.delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.having('SUM(id) < 3').delete_all }
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all }
+ end
+
+ def test_select_with_aggregates
+ posts = Post.select(:title, :body)
+
+ assert_equal 11, posts.count(:all)
+ assert_equal 11, posts.size
+ assert posts.any?
+ assert posts.many?
+ assert_not posts.empty?
end
def test_select_takes_a_variable_list_of_args
@@ -854,6 +886,17 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 9, posts.where(:comments_count => 0).count
end
+ def test_count_on_association_relation
+ author = Author.last
+ another_author = Author.first
+ posts = Post.where(author_id: author.id)
+
+ assert_equal author.posts.where(author_id: author.id).size, posts.count
+
+ assert_equal 0, author.posts.where(author_id: another_author.id).size
+ assert author.posts.where(author_id: another_author.id).empty?
+ end
+
def test_count_with_distinct
posts = Post.all
@@ -864,6 +907,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 11, posts.distinct(false).select(:comments_count).count
end
+ def test_update_all_with_scope
+ tag = Tag.first
+ Post.tagged_with(tag.id).update_all title: "rofl"
+ list = Post.tagged_with(tag.id).all.to_a
+ assert_operator list.length, :>, 0
+ list.each { |post| assert_equal 'rofl', post.title }
+ end
+
def test_count_explicit_columns
Post.update_all(:comments_count => nil)
posts = Post.all
@@ -1424,6 +1475,18 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [], scope.references_values
end
+ def test_order_with_reorder_nil_removes_the_order
+ relation = Post.order(:title).reorder(nil)
+
+ assert_nil relation.order_values.first
+ end
+
+ def test_reverse_order_with_reorder_nil_removes_the_order
+ relation = Post.order(:title).reverse_order.reorder(nil)
+
+ assert_nil relation.order_values.first
+ end
+
def test_presence
topics = Topic.all
@@ -1599,10 +1662,8 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_removes_rhs_bind_parameters
- left = Post.where(id: Arel::Nodes::BindParam.new('?'))
- column = Post.columns_hash['id']
- left.bind_values += [[column, 20]]
- right = Post.where(id: 10)
+ left = Post.where(id: 20)
+ right = Post.where(id: [1,2,3,4])
merged = left.merge(right)
assert_equal [], merged.bind_values
@@ -1612,8 +1673,7 @@ class RelationTest < ActiveRecord::TestCase
column = Post.columns_hash['id']
binds = [[column, 20]]
- right = Post.where(id: Arel::Nodes::BindParam.new('?'))
- right.bind_values += binds
+ right = Post.where(id: 20)
left = Post.where(id: 10)
merged = left.merge(right)
@@ -1621,19 +1681,15 @@ class RelationTest < ActiveRecord::TestCase
end
def test_merging_reorders_bind_params
- post = Post.first
- id_column = Post.columns_hash['id']
- title_column = Post.columns_hash['title']
-
- bv = Post.connection.substitute_at id_column, 0
-
- right = Post.where(id: bv)
- right.bind_values += [[id_column, post.id]]
-
- left = Post.where(title: bv)
- left.bind_values += [[title_column, post.title]]
+ post = Post.first
+ right = Post.where(id: post.id)
+ left = Post.where(title: post.title)
merged = left.merge(right)
assert_equal post, merged.first
end
+
+ def test_relation_join_method
+ assert_equal 'Thank you for the welcome,Thank you again for the welcome', Post.first.comments.join(",")
+ end
end
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 954eab8022..dca85fb5eb 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -51,4 +51,31 @@ class SanitizeTest < ActiveRecord::TestCase
select_author_sql = Post.send(:sanitize_sql_array, [''])
assert_equal('', select_author_sql)
end
+
+ def test_sanitize_sql_like
+ assert_equal '100\%', Binary.send(:sanitize_sql_like, '100%')
+ assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, 'snake_cased_string')
+ assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint')
+ assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42')
+ end
+
+ def test_sanitize_sql_like_with_custom_escape_character
+ assert_equal '100!%', Binary.send(:sanitize_sql_like, '100%', '!')
+ assert_equal 'snake!_cased!_string', Binary.send(:sanitize_sql_like, 'snake_cased_string', '!')
+ assert_equal 'great!!', Binary.send(:sanitize_sql_like, 'great!', '!')
+ assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', '!')
+ assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42', '!')
+ end
+
+ def test_sanitize_sql_like_example_use_case
+ searchable_post = Class.new(Post) do
+ def self.search(term)
+ where("title LIKE ?", sanitize_sql_like(term, '!'))
+ end
+ end
+
+ assert_sql(/LIKE '20!% !_reduction!_!!'/) do
+ searchable_post.search("20% _reduction_!").to_a
+ end
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 575eb34a9c..fd0ef2f89f 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -63,7 +63,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/)
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid)\s+"/)
match[0].length
end
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index 76da49707f..a704b861cb 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -10,27 +10,61 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
+ #Cache v 1.1 tests
+ def test_statement_cache
+ Book.create(name: "my book")
+ Book.create(name: "my other book")
+
+ cache = StatementCache.create(Book.connection) do |params|
+ Book.where(:name => params.bind)
+ end
+
+ b = cache.execute([ "my book" ], Book, Book.connection)
+ assert_equal "my book", b[0].name
+ b = cache.execute([ "my other book" ], Book, Book.connection)
+ assert_equal "my other book", b[0].name
+ end
+
+
+ def test_statement_cache_id
+ b1 = Book.create(name: "my book")
+ b2 = Book.create(name: "my other book")
+
+ cache = StatementCache.create(Book.connection) do |params|
+ Book.where(id: params.bind)
+ end
+
+ b = cache.execute([ b1.id ], Book, Book.connection)
+ assert_equal b1.name, b[0].name
+ b = cache.execute([ b2.id ], Book, Book.connection)
+ assert_equal b2.name, b[0].name
+ end
+
+ def test_find_or_create_by
+ Book.create(name: "my book")
+
+ a = Book.find_or_create_by(name: "my book")
+ b = Book.find_or_create_by(name: "my other book")
+
+ assert_equal("my book", a.name)
+ assert_equal("my other book", b.name)
+ end
+
+ #End
+
def test_statement_cache_with_simple_statement
- cache = ActiveRecord::StatementCache.new do
+ cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book").where("author_id > 3")
end
Book.create(name: "my book", author_id: 4)
- books = cache.execute
+ books = cache.execute([], Book, Book.connection)
assert_equal "my book", books[0].name
end
- def test_statement_cache_with_nil_statement_raises_error
- assert_raise(ArgumentError) do
- ActiveRecord::StatementCache.new do
- nil
- end
- end
- end
-
def test_statement_cache_with_complex_statement
- cache = ActiveRecord::StatementCache.new do
+ cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton')
end
@@ -38,12 +72,12 @@ module ActiveRecord
molecule = salty.molecules.create(name: 'dioxane')
molecule.electrons.create(name: 'lepton')
- liquids = cache.execute
+ liquids = cache.execute([], Book, Book.connection)
assert_equal "salty", liquids[0].name
end
def test_statement_cache_values_differ
- cache = ActiveRecord::StatementCache.new do
+ cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book")
end
@@ -51,13 +85,13 @@ module ActiveRecord
Book.create(name: "my book")
end
- first_books = cache.execute
+ first_books = cache.execute([], Book, Book.connection)
3.times do
Book.create(name: "my book")
end
- additional_books = cache.execute
+ additional_books = cache.execute([], Book, Book.connection)
assert first_books != additional_books
end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 978cee9cfb..6a34c55011 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -163,6 +163,22 @@ class StoreTest < ActiveRecord::TestCase
assert_equal [:width, :height], second_model.stored_attributes[:data]
end
+ test "stored_attributes are tracked per subclass" do
+ first_model = Class.new(ActiveRecord::Base) do
+ store_accessor :data, :color
+ end
+ second_model = Class.new(first_model) do
+ store_accessor :data, :width, :height
+ end
+ third_model = Class.new(first_model) do
+ store_accessor :data, :area, :volume
+ end
+
+ assert_equal [:color], first_model.stored_attributes[:data]
+ assert_equal [:color, :width, :height], second_model.stored_attributes[:data]
+ assert_equal [:color, :area, :volume], third_model.stored_attributes[:data]
+ end
+
test "YAML coder initializes the store when a Nil value is given" do
assert_equal({}, @john.params)
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 803a054d7e..b6c5511849 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -10,13 +10,7 @@ module ActiveRecord
end
def assert_date_from_db(expected, actual, message = nil)
- # SybaseAdapter doesn't have a separate column type just for dates,
- # so the time is in the string and incorrectly formatted
- if current_adapter?(:SybaseAdapter)
- assert_equal expected.to_s, actual.to_date.to_s, message
- else
- assert_equal expected.to_s, actual.to_s, message
- end
+ assert_equal expected.to_s, actual.to_s, message
end
def capture_sql
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 594b4fb07b..77ab427be0 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -112,7 +112,7 @@ class TimestampTest < ActiveRecord::TestCase
previous_starting = task.starting
previous_ending = task.ending
task.touch(:starting, :ending)
-
+
assert_not_equal previous_starting, task.starting
assert_not_equal previous_ending, task.ending
assert_in_delta Time.now, task.starting, 1
@@ -170,6 +170,25 @@ class TimestampTest < ActiveRecord::TestCase
assert !@developer.no_touching?
end
+ def test_no_touching_with_callbacks
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "developers"
+
+ attr_accessor :after_touch_called
+
+ after_touch do |user|
+ user.after_touch_called = true
+ end
+ end
+
+ developer = klass.first
+
+ klass.no_touching do
+ developer.touch
+ assert_not developer.after_touch_called
+ end
+ end
+
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
pet = Pet.first
owner = pet.owner
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index e6ed85394b..de1f624191 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -123,6 +123,19 @@ class TransactionTest < ActiveRecord::TestCase
assert !Topic.find(1).approved?
end
+ def test_rolling_back_in_a_callback_rollbacks_before_save
+ def @first.before_save_for_transaction
+ raise ActiveRecord::Rollback
+ end
+ assert !@first.approved
+
+ Topic.transaction do
+ @first.approved = true
+ @first.save!
+ end
+ assert !Topic.find(@first.id).approved?, "Should not commit the approved flag"
+ end
+
def test_raising_exception_in_nested_transaction_restore_state_in_save
topic = Topic.new
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 602f633c45..e4edc437e6 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -1,44 +1,14 @@
-# encoding: utf-8
require "cases/helper"
require 'models/topic'
require 'models/reply'
-require 'models/owner'
-require 'models/pet'
require 'models/man'
require 'models/interest'
class AssociationValidationTest < ActiveRecord::TestCase
- fixtures :topics, :owners
+ fixtures :topics
repair_validations(Topic, Reply)
- def test_validates_size_of_association
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'apet')
- assert o.valid?
- end
- end
-
- def test_validates_size_of_association_using_within
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
- o = Owner.new('name' => 'nopets')
- assert !o.save
- assert o.errors[:pets].any?
-
- o.pets.build('name' => 'apet')
- assert o.valid?
-
- 2.times { o.pets.build('name' => 'apet') }
- assert !o.save
- assert o.errors[:pets].any?
- end
- end
-
def test_validates_associated_many
Topic.validates_associated(:replies)
Reply.validates_presence_of(:content)
@@ -94,17 +64,6 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert r.valid?
end
- def test_validates_size_of_association_utf8
- repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'あいうえおかきくけこ')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'あいうえおかきくけこ')
- assert o.valid?
- end
- end
-
def test_validates_presence_of_belongs_to_association__parent_is_new_record
repair_validations(Interest) do
# Note that Interest and Man have the :inverse_of option set
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
new file mode 100644
index 0000000000..4a92da38ce
--- /dev/null
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'models/owner'
+require 'models/pet'
+
+class LengthValidationTest < ActiveRecord::TestCase
+ fixtures :owners
+ repair_validations(Owner)
+
+ def test_validates_size_of_association
+ repair_validations Owner do
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'apet')
+ assert o.valid?
+ end
+ end
+
+ def test_validates_size_of_association_using_within
+ repair_validations Owner do
+ assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 }
+ o = Owner.new('name' => 'nopets')
+ assert !o.save
+ assert o.errors[:pets].any?
+
+ o.pets.build('name' => 'apet')
+ assert o.valid?
+
+ 2.times { o.pets.build('name' => 'apet') }
+ assert !o.save
+ assert o.errors[:pets].any?
+ end
+ end
+
+ def test_validates_size_of_association_utf8
+ repair_validations Owner do
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'あいうえおかきくけこ')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'あいうえおかきくけこ')
+ assert o.valid?
+ end
+ end
+end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index d80da06e27..a6e1dc72e5 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -14,28 +14,28 @@ class ValidationsTest < ActiveRecord::TestCase
# Other classes we mess with will be dealt with in the specific tests
repair_validations(Topic)
- def test_error_on_create
+ def test_valid_uses_create_context_when_new
r = WrongReply.new
r.title = "Wrong Create"
- assert !r.save
+ assert_not r.valid?
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
assert_equal ["is Wrong Create"], r.errors[:title], "A reply with a bad content should contain an error"
end
- def test_error_on_update
+ def test_valid_uses_update_context_when_persisted
r = WrongReply.new
r.title = "Bad"
r.content = "Good"
- assert r.save, "First save should be successful"
+ assert r.save, "First validation should be successful"
r.title = "Wrong Update"
- assert !r.save, "Second save should fail"
+ assert_not r.valid?, "Second validation should fail"
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
assert_equal ["is Wrong Update"], r.errors[:title], "A reply with a bad content should contain an error"
end
- def test_error_on_given_context
+ def test_valid_using_special_context
r = WrongReply.new(:title => "Valid title")
assert !r.valid?(:special_case)
assert_equal "Invalid", r.errors[:author_name].join
@@ -45,11 +45,11 @@ class ValidationsTest < ActiveRecord::TestCase
assert r.valid?(:special_case)
r.author_name = nil
- assert !r.save(:context => :special_case)
+ assert_not r.valid?(:special_case)
assert_equal "Invalid", r.errors[:author_name].join
r.author_name = "secret"
- assert r.save(:context => :special_case)
+ assert r.valid?(:special_case)
end
def test_validate
@@ -100,7 +100,7 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
- def test_create_without_validation
+ def test_save_without_validation
reply = WrongReply.new
assert !reply.save
assert reply.save(:validate => false)
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 3cb617497d..1a690c01a6 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -226,7 +226,6 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
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
@@ -248,14 +247,9 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
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 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']
# Oracle and DB2 don't have true boolean or time-only fields
unless current_adapter?(:OracleAdapter, :DB2Adapter)
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index 479b8c050d..a54914c372 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -51,28 +51,6 @@ connections:
password: arunit
database: arunit2
- firebird:
- arunit:
- host: localhost
- username: rails
- password: rails
- charset: UTF8
- arunit2:
- host: localhost
- username: rails
- password: rails
- charset: UTF8
-
- frontbase:
- arunit:
- host: localhost
- username: rails
- session_name: unittest-<%= $$ %>
- arunit2:
- host: localhost
- username: rails
- session_name: unittest-<%= $$ %>
-
mysql:
arunit:
username: rails
@@ -130,11 +108,3 @@ connections:
arunit2:
adapter: sqlite3
database: ':memory:'
-
- sybase:
- arunit:
- host: database_ASE
- username: sa
- arunit2:
- host: database_ASE
- username: sa
diff --git a/activerecord/test/fixtures/uuid_children.yml b/activerecord/test/fixtures/uuid_children.yml
new file mode 100644
index 0000000000..a7b15016e2
--- /dev/null
+++ b/activerecord/test/fixtures/uuid_children.yml
@@ -0,0 +1,3 @@
+sonny:
+ uuid_parent: daddy
+ name: Sonny
diff --git a/activerecord/test/fixtures/uuid_parents.yml b/activerecord/test/fixtures/uuid_parents.yml
new file mode 100644
index 0000000000..0b40225c5c
--- /dev/null
+++ b/activerecord/test/fixtures/uuid_parents.yml
@@ -0,0 +1,2 @@
+daddy:
+ name: Daddy
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index c197951c71..8949cf5826 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -140,6 +140,8 @@ class Author < ActiveRecord::Base
has_many :posts_with_default_include, :class_name => 'PostWithDefaultInclude'
has_many :comments_on_posts_with_default_include, :through => :posts_with_default_include, :source => :comments
+ has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post"
+
scope :relation_include_posts, -> { includes(:posts) }
scope :relation_include_tags, -> { includes(:tags) }
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 566e0873f1..a762ad4bb5 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -6,6 +6,8 @@ class Club < ActiveRecord::Base
has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member"
belongs_to :category
+ has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member
+
private
def private_method
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index ede5fbd0c6..15970758db 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -7,6 +7,9 @@ class Comment < ActiveRecord::Base
scope :created, -> { all }
belongs_to :post, :counter_cache => true
+ belongs_to :author, polymorphic: true
+ belongs_to :resource, polymorphic: true
+
has_many :ratings
belongs_to :first_post, :foreign_key => :post_id
@@ -26,6 +29,10 @@ class Comment < ActiveRecord::Base
all
end
scope :all_as_scope, -> { all }
+
+ def to_s
+ body
+ end
end
class SpecialComment < Comment
@@ -36,3 +43,11 @@ end
class VerySpecialComment < Comment
end
+
+class CommentThatAutomaticallyAltersPostBody < Comment
+ belongs_to :post, class_name: "PostThatLoadsCommentsInAnAfterSaveHook", foreign_key: :post_id
+
+ after_save do |comment|
+ comment.post.update_attributes(body: "Automatically altered")
+ end
+end
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 38b0b6aafa..dae102d12b 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -46,6 +46,24 @@ module MyApplication
end
end
end
+
+ module Suffixed
+ def self.table_name_suffix
+ '_suffixed'
+ end
+
+ class Company < ActiveRecord::Base
+ end
+
+ class Firm < Company
+ self.table_name = 'companies'
+ end
+
+ module Nested
+ class Company < ActiveRecord::Base
+ end
+ end
+ end
end
module Billing
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 762259ffa3..5bd2f00129 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -13,6 +13,8 @@ class Developer < ActiveRecord::Base
end
end
+ accepts_nested_attributes_for :projects
+
has_and_belongs_to_many :projects_extended_by_name,
-> { extending(DeveloperProjectsAssociationExtension) },
:class_name => "Project",
@@ -74,12 +76,6 @@ class AuditLog < ActiveRecord::Base
belongs_to :unvalidated_developer, :class_name => 'Developer'
end
-DeveloperSalary = Struct.new(:amount)
-class DeveloperWithAggregate < ActiveRecord::Base
- self.table_name = 'developers'
- composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)]
-end
-
class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
self.table_name = 'developers'
has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id'
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 7bb0caf44b..90a3c3ecee 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -18,7 +18,6 @@ class Pirate < ActiveRecord::Base
has_many :treasures, :as => :looter
has_many :treasure_estimates, :through => :treasures, :source => :price_estimates
- # These both have :autosave enabled because accepts_nested_attributes_for is used on them.
has_one :ship
has_one :update_only_ship, :class_name => 'Ship'
has_one :non_validated_ship, :class_name => 'Ship'
@@ -85,3 +84,9 @@ end
class DestructivePirate < Pirate
has_one :dependent_ship, :class_name => 'Ship', :foreign_key => :pirate_id, :dependent => :destroy
end
+
+class FamousPirate < ActiveRecord::Base
+ self.table_name = 'pirates'
+ has_many :famous_ships
+ validates_presence_of :catchphrase, on: :conference
+end \ No newline at end of file
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 099e039255..5f01ab0a82 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -40,6 +40,8 @@ class Post < ActiveRecord::Base
scope :with_comments, -> { preload(:comments) }
scope :with_tags, -> { preload(:taggings) }
+ scope :tagged_with, ->(id) { joins(:taggings).where(taggings: { tag_id: id }) }
+
has_many :comments do
def find_most_recent
order("id DESC").first
@@ -145,6 +147,10 @@ class Post < ActiveRecord::Base
has_many :lazy_readers
has_many :lazy_readers_skimmers_or_not, -> { where(skimmer: [ true, false ]) }, :class_name => 'LazyReader'
+ has_many :lazy_people, :through => :lazy_readers, :source => :person
+ has_many :lazy_readers_unscope_skimmers, -> { skimmers_or_not }, :class_name => 'LazyReader'
+ has_many :lazy_people_unscope_skimmers, :through => :lazy_readers_unscope_skimmers, :source => :person
+
def self.top(limit)
ranked_by_comments.limit_by(limit)
end
@@ -161,10 +167,6 @@ class Post < ActiveRecord::Base
return @log if message.nil?
@log << [message, side, new_record]
end
-
- def self.what_are_you
- 'a post...'
- end
end
class SpecialPost < Post; end
@@ -206,3 +208,12 @@ class SpecialPostWithDefaultScope < ActiveRecord::Base
self.table_name = 'posts'
default_scope { where(:id => [1, 5,6]) }
end
+
+class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base
+ self.table_name = 'posts'
+ has_many :comments, class_name: "CommentThatAutomaticallyAltersPostBody", foreign_key: :post_id
+
+ after_save do |post|
+ post.comments.load
+ end
+end
diff --git a/activerecord/test/models/publisher.rb b/activerecord/test/models/publisher.rb
new file mode 100644
index 0000000000..0d4a7f9235
--- /dev/null
+++ b/activerecord/test/models/publisher.rb
@@ -0,0 +1,2 @@
+module Publisher
+end
diff --git a/activerecord/test/models/publisher/article.rb b/activerecord/test/models/publisher/article.rb
new file mode 100644
index 0000000000..03a277bbdd
--- /dev/null
+++ b/activerecord/test/models/publisher/article.rb
@@ -0,0 +1,3 @@
+class Publisher::Article < ActiveRecord::Base
+ has_and_belongs_to_many :magazines
+end
diff --git a/activerecord/test/models/publisher/magazine.rb b/activerecord/test/models/publisher/magazine.rb
new file mode 100644
index 0000000000..82e1a14008
--- /dev/null
+++ b/activerecord/test/models/publisher/magazine.rb
@@ -0,0 +1,3 @@
+class Publisher::Magazine < ActiveRecord::Base
+ has_and_belongs_to_many :articles
+end
diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb
index 3a6b7fad34..91afc1898c 100644
--- a/activerecord/test/models/reader.rb
+++ b/activerecord/test/models/reader.rb
@@ -16,6 +16,8 @@ class LazyReader < ActiveRecord::Base
self.table_name = "readers"
default_scope -> { where(skimmer: true) }
+ scope :skimmers_or_not, -> { unscope(:where => :skimmer) }
+
belongs_to :post
belongs_to :person
end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 3da031946f..77a4728d0b 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -17,3 +17,9 @@ class Ship < ActiveRecord::Base
false
end
end
+
+class FamousShip < ActiveRecord::Base
+ self.table_name = 'ships'
+ belongs_to :famous_pirate
+ validates_presence_of :name, on: :conference
+end
diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb
index a581b381e8..80d4725f7e 100644
--- a/activerecord/test/models/tag.rb
+++ b/activerecord/test/models/tag.rb
@@ -3,5 +3,5 @@ class Tag < ActiveRecord::Base
has_many :taggables, :through => :taggings
has_one :tagging
- has_many :tagged_posts, :through => :taggings, :source => :taggable, :source_type => 'Post'
-end \ No newline at end of file
+ has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post'
+end
diff --git a/activerecord/test/models/uuid_child.rb b/activerecord/test/models/uuid_child.rb
new file mode 100644
index 0000000000..a3d0962ad6
--- /dev/null
+++ b/activerecord/test/models/uuid_child.rb
@@ -0,0 +1,3 @@
+class UuidChild < ActiveRecord::Base
+ belongs_to :uuid_parent
+end
diff --git a/activerecord/test/models/uuid_parent.rb b/activerecord/test/models/uuid_parent.rb
new file mode 100644
index 0000000000..5634f22d0c
--- /dev/null
+++ b/activerecord/test/models/uuid_parent.rb
@@ -0,0 +1,3 @@
+class UuidParent < ActiveRecord::Base
+ has_many :uuid_children
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index d9e1745029..8c52ad2724 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -9,13 +9,14 @@ ActiveRecord::Schema.define do
#put adapter specific setup here
case adapter_name
- # For Firebird, set the sequence values 10000 when create_table is called;
- # this prevents primary key collisions between "normally" created records
- # and fixture-based (YAML) records.
- when "Firebird"
- def create_table(*args, &block)
- ActiveRecord::Base.connection.create_table(*args, &block)
- ActiveRecord::Base.connection.execute "SET GENERATOR #{args.first}_seq TO 10000"
+ when "PostgreSQL"
+ enable_uuid_ossp!(ActiveRecord::Base.connection)
+ create_table :uuid_parents, id: :uuid, force: true do |t|
+ t.string :name
+ end
+ create_table :uuid_children, id: :uuid, force: true do |t|
+ t.string :name
+ t.uuid :uuid_parent_id
end
end
@@ -53,6 +54,14 @@ ActiveRecord::Schema.define do
t.string :name
end
+ create_table :articles, force: true do |t|
+ end
+
+ create_table :articles_magazines, force: true do |t|
+ t.references :article
+ t.references :magazine
+ end
+
create_table :audit_logs, force: true do |t|
t.column :message, :string, null: false
t.column :developer_id, :integer, null: false
@@ -178,6 +187,9 @@ ActiveRecord::Schema.define do
t.integer :taggings_count, default: 0
t.integer :children_count, default: 0
t.integer :parent_id
+ t.references :author, polymorphic: true
+ t.string :resource_id
+ t.string :resource_type
end
create_table :companies, force: true do |t|
@@ -376,6 +388,9 @@ ActiveRecord::Schema.define do
t.column :custom_lock_version, :integer
end
+ create_table :magazines, force: true do |t|
+ end
+
create_table :mateys, id: false, force: true do |t|
t.column :pirate_id, :integer
t.column :target_id, :integer
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index f65d9ea120..2a992f60bb 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,102 @@
+* `Hash#deep_transform_keys` and `Hash#deep_transform_keys!` now transform hashes
+ in nested arrays. This change also applies to `Hash#deep_stringify_keys`,
+ `Hash#deep_stringify_keys!`, `Hash#deep_symbolize_keys` and
+ `Hash#deep_symbolize_keys!`.
+
+ *OZAWA Sakuro*
+
+* Fixed confusing `DelegationError` in `Module#delegate`.
+
+ See #15186.
+
+ *Vladimir Yarotsky*
+
+* Fixed `ActiveSupport::Subscriber` so that no duplicate subscriber is created
+ when a subscriber method is redefined.
+
+ *Dennis Schön*
+
+* Remove deprecated string based terminators for `ActiveSupport::Callbacks`.
+
+ *Eileen M. Uchitelle*
+
+* Fixed an issue when using
+ `ActiveSupport::NumberHelper::NumberToDelimitedConverter` to
+ convert a value that is an `ActiveSupport::SafeBuffer` introduced
+ in 2da9d67.
+
+ See #15064.
+
+ *Mark J. Titorenko*
+
+* `TimeZone#parse` defaults the day of the month to '1' if any other date
+ components are specified. This is more consistent with the behavior of
+ `Time#parse`.
+
+ *Ulysse Carion*
+
+* `humanize` strips leading underscores, if any.
+
+ Before:
+
+ '_id'.humanize # => ""
+
+ After:
+
+ '_id'.humanize # => "Id"
+
+ *Xavier Noria*
+
+* Fixed backward compatibility isues introduced in 326e652.
+
+ Empty Hash or Array should not present in serialization result.
+
+ {a: []}.to_query # => ""
+ {a: {}}.to_query # => ""
+
+ For more info see #14948.
+
+ *Bogdan Gusiev*
+
+* Add `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5` to support stable
+ UUID fixtures on PostgreSQL.
+
+ *Roderick van Domburg*
+
+* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is
+ true.
+
+ This fixes the current situation of:
+
+ 1.second.eql?(1.second) #=> false
+
+ `eql?` also requires that the other object is an `ActiveSupport::Duration`.
+ This requirement makes `ActiveSupport::Duration`'s behavior consistent with
+ the behavior of Ruby's numeric types:
+
+ 1.eql?(1.0) #=> false
+ 1.0.eql?(1) #=> false
+
+ 1.second.eql?(1) #=> false (was true)
+ 1.eql?(1.second) #=> false
+
+ { 1 => "foo", 1.0 => "bar" }
+ #=> { 1 => "foo", 1.0 => "bar" }
+
+ { 1 => "foo", 1.second => "bar" }
+ # now => { 1 => "foo", 1.second => "bar" }
+ # was => { 1 => "bar" }
+
+ And though the behavior of these hasn't changed, for reference:
+
+ 1 == 1.0 #=> true
+ 1.0 == 1 #=> true
+
+ 1 == 1.second #=> true
+ 1.second == 1 #=> true
+
+ *Emily Dobervich*
+
* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies
instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is
deprecated.
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index d58578b7bc..1fec1bea0d 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -65,14 +65,14 @@ module ActiveSupport
@silencers << block
end
- # Will remove all silencers, but leave in the filters. This is useful if
- # your context of debugging suddenly expands as you suspect a bug in one of
+ # Removes all silencers, but leaves in the filters. Useful if your
+ # context of debugging suddenly expands as you suspect a bug in one of
# the libraries you use.
def remove_silencers!
@silencers = []
end
- # Removes all filters, but leaves in silencers. Useful if you suddenly
+ # Removes all filters, but leaves in the silencers. Useful if you suddenly
# need to see entire filepaths in the backtrace that you had already
# filtered out.
def remove_filters!
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index e9ee98a128..73c6b3cb88 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/string/inflections'
+require 'active_support/per_thread_registry'
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e14ece7f35..06505bddf9 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -131,8 +131,6 @@ module ActiveSupport
end
end
- private
-
def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
lambda { |env|
target = env.target
@@ -149,6 +147,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :halting_and_conditional
def self.halting(next_callback, user_callback, halted_lambda, filter)
lambda { |env|
@@ -166,6 +165,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :halting
def self.conditional(next_callback, user_callback, user_conditions)
lambda { |env|
@@ -178,6 +178,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :conditional
def self.simple(next_callback, user_callback)
lambda { |env|
@@ -185,6 +186,7 @@ module ActiveSupport
next_callback.call env
}
end
+ private_class_method :simple
end
class After
@@ -208,8 +210,6 @@ module ActiveSupport
end
end
- private
-
def self.halting_and_conditional(next_callback, user_callback, user_conditions)
lambda { |env|
env = next_callback.call env
@@ -223,6 +223,7 @@ module ActiveSupport
env
}
end
+ private_class_method :halting_and_conditional
def self.halting(next_callback, user_callback)
lambda { |env|
@@ -233,6 +234,7 @@ module ActiveSupport
env
}
end
+ private_class_method :halting
def self.conditional(next_callback, user_callback, user_conditions)
lambda { |env|
@@ -246,6 +248,7 @@ module ActiveSupport
env
}
end
+ private_class_method :conditional
def self.simple(next_callback, user_callback)
lambda { |env|
@@ -254,6 +257,7 @@ module ActiveSupport
env
}
end
+ private_class_method :simple
end
class Around
@@ -269,8 +273,6 @@ module ActiveSupport
end
end
- private
-
def self.halting_and_conditional(next_callback, user_callback, user_conditions)
lambda { |env|
target = env.target
@@ -288,23 +290,25 @@ module ActiveSupport
end
}
end
+ private_class_method :halting_and_conditional
def self.halting(next_callback, user_callback)
lambda { |env|
target = env.target
value = env.value
- unless env.halted
+ if env.halted
+ next_callback.call env
+ else
user_callback.call(target, value) {
env = next_callback.call env
env.value
}
env
- else
- next_callback.call env
end
}
end
+ private_class_method :halting
def self.conditional(next_callback, user_callback, user_conditions)
lambda { |env|
@@ -322,6 +326,7 @@ module ActiveSupport
end
}
end
+ private_class_method :conditional
def self.simple(next_callback, user_callback)
lambda { |env|
@@ -332,6 +337,7 @@ module ActiveSupport
env
}
end
+ private_class_method :simple
end
end
@@ -718,12 +724,6 @@ module ActiveSupport
# would call <tt>Audit#save</tt>.
def define_callbacks(*names)
options = names.extract_options!
- if options.key?(:terminator) && String === options[:terminator]
- ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda"
- value = options[:terminator]
- line = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__
- options[:terminator] = lambda { |target, result| target.instance_exec(result, &line) }
- end
names.each do |name|
class_attribute "_#{name}_callbacks"
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 6c3e48a3ca..2149d4439d 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -221,7 +221,7 @@ module ActiveSupport
def garbage?(value)
# If the type is the only element which makes it then
# this still makes the value nil, except if type is
- # a XML node(where type['value'] is a Hash)
+ # an XML node(where type['value'] is a Hash)
value['type'] && !value['type'].is_a?(::Hash) && value.size == 1
end
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
index dc86c92003..763d563231 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,27 +1,38 @@
class Hash
# Returns a new hash with +self+ and +other_hash+ merged recursively.
#
- # h1 = { x: { y: [4, 5, 6] }, z: [7, 8, 9] }
- # h2 = { x: { y: [7, 8, 9] }, z: 'xyz' }
+ # h1 = { a: true, b: { c: [1, 2, 3] } }
+ # h2 = { a: false, b: { x: [3, 4, 5] } }
#
- # h1.deep_merge(h2) # => {x: {y: [7, 8, 9]}, z: "xyz"}
- # h2.deep_merge(h1) # => {x: {y: [4, 5, 6]}, z: [7, 8, 9]}
- # h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
- # # => {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
+ # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
+ #
+ # Like with Hash#merge in the standard library, a block can be provided
+ # to merge values:
+ #
+ # h1 = { a: 100, b: 200, c: { c1: 100 } }
+ # h2 = { b: 250, c: { c1: 200 } }
+ # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
+ # # => { a: 100, b: 450, c: { c1: 300 } }
def deep_merge(other_hash, &block)
dup.deep_merge!(other_hash, &block)
end
# Same as +deep_merge+, but modifies +self+.
def deep_merge!(other_hash, &block)
- other_hash.each_pair do |k,v|
- tv = self[k]
- if tv.is_a?(Hash) && v.is_a?(Hash)
- self[k] = tv.deep_merge(v, &block)
+ other_hash.each_pair do |current_key, other_value|
+ this_value = self[current_key]
+
+ self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
+ this_value.deep_merge(other_value, &block)
else
- self[k] = block && tv ? block.call(k, tv, v) : v
+ if block_given? && key?(current_key)
+ block.call(current_key, this_value, other_value)
+ else
+ other_value
+ end
end
end
+
self
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 3d41aa8572..28536e32a4 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -75,34 +75,26 @@ class Hash
# Returns a new hash with all keys converted by the block operation.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
#
# hash = { person: { name: 'Rob', age: '28' } }
#
# hash.deep_transform_keys{ |key| key.to_s.upcase }
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
def deep_transform_keys(&block)
- result = {}
- each do |key, value|
- result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
- end
- result
+ _deep_transform_keys_in_object(self, &block)
end
# Destructively convert all keys by using the block operation.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
def deep_transform_keys!(&block)
- keys.each do |key|
- value = delete(key)
- self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
- end
- self
+ _deep_transform_keys_in_object!(self, &block)
end
# Returns a new hash with all keys converted to strings.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
#
# hash = { person: { name: 'Rob', age: '28' } }
#
@@ -114,14 +106,14 @@ class Hash
# Destructively convert all keys to strings.
# This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
def deep_stringify_keys!
deep_transform_keys!{ |key| key.to_s }
end
# Returns a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+. This includes the keys from the root hash
- # and from all nested hashes.
+ # and from all nested hashes and arrays.
#
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
#
@@ -133,8 +125,38 @@ class Hash
# Destructively convert all keys to symbols, as long as they respond
# to +to_sym+. This includes the keys from the root hash and from all
- # nested hashes.
+ # nested hashes and arrays.
def deep_symbolize_keys!
deep_transform_keys!{ |key| key.to_sym rescue key }
end
+
+ private
+ # support methods for deep transforming nested hashes and arrays
+ def _deep_transform_keys_in_object(object, &block)
+ case object
+ when Hash
+ object.each_with_object({}) do |(key, value), result|
+ result[yield(key)] = _deep_transform_keys_in_object(value, &block)
+ end
+ when Array
+ object.map {|e| _deep_transform_keys_in_object(e, &block) }
+ else
+ object
+ end
+ end
+
+ def _deep_transform_keys_in_object!(object, &block)
+ case object
+ when Hash
+ object.keys.each do |key|
+ value = object.delete(key)
+ object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
+ end
+ object
+ when Array
+ object.map! {|e| _deep_transform_keys_in_object!(e, &block)}
+ else
+ object
+ end
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index aa19aed43b..293a3b2619 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/kernel/agnostics'
require 'active_support/core_ext/kernel/concern'
-require 'active_support/core_ext/kernel/debugger'
+require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index fe24f3716d..768b980f21 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -7,6 +7,7 @@ class LoadError
]
unless method_defined?(:path)
+ # Returns the path which was unable to be loaded.
def path
@path ||= begin
REGEXPS.find do |regex|
@@ -17,9 +18,11 @@ class LoadError
end
end
+ # Returns true if the given path name (except perhaps for the ".rb"
+ # extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
end
end
-MissingSourceFile = LoadError \ No newline at end of file
+MissingSourceFile = LoadError
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index f855833a24..e926392952 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -170,38 +170,26 @@ class Module
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
- # The following generated methods call the target exactly once, storing
+ # The following generated method calls the target exactly once, storing
# the returned value in a dummy variable.
#
# Reason is twofold: On one hand doing less calls is in general better.
# On the other hand it could be that the target has side-effects,
# whereas conceptually, from the user point of view, the delegator should
# be doing one call.
- if allow_nil
- method_def = [
- "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block)
- "_ = #{to}", # _ = client
- "if !_.nil? || nil.respond_to?(:#{method})", # if !_.nil? || nil.respond_to?(:name)
- " _.#{method}(#{definition})", # _.name(*args, &block)
- "end", # end
- "end" # end
- ].join ';'
- else
- exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
- method_def = [
- "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block)
- " _ = #{to}", # _ = client
- " _.#{method}(#{definition})", # _.name(*args, &block)
- "rescue NoMethodError => e", # rescue NoMethodError => e
- " if _.nil? && e.name == :#{method}", # if _.nil? && e.name == :name
- " #{exception}", # # add helpful message to the exception
- " else", # else
- " raise", # raise
- " end", # end
- "end" # end
- ].join ';'
- end
+ exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
+
+ method_def = [
+ "def #{method_prefix}#{method}(#{definition})",
+ " _ = #{to}",
+ " if !_.nil? || nil.respond_to?(:#{method})",
+ " _.#{method}(#{definition})",
+ " else",
+ " #{exception unless allow_nil}",
+ " end",
+ "end"
+ ].join ';'
module_eval(method_def, file, line)
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 9cd7485e2e..3d2c809c9f 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -78,6 +78,9 @@ end
require 'bigdecimal'
class BigDecimal
+ # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
+ # raises TypeError exception. Checking here on the runtime whether BigDecimal
+ # will allow dup or not.
begin
BigDecimal.new('4.56').dup
diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb
deleted file mode 100644
index f58364f9c6..0000000000
--- a/activesupport/lib/active_support/core_ext/object/to_json.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/object/to_json`. ' \
- 'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \
- 'instead.'
-
-require 'active_support/core_ext/object/json' \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 13be0038c2..e65fc5bac1 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -51,12 +51,10 @@ class Hash
#
# This method is also aliased as +to_query+.
def to_param(namespace = nil)
- if empty?
- namespace ? nil.to_query(namespace) : ''
- else
- collect do |key, value|
+ collect do |key, value|
+ unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
- end.sort! * '&'
- end
+ end
+ end.compact.sort! * '&'
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb
index 37352fa608..172f06ed64 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/to_param'
+require 'cgi'
class Object
# Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the
@@ -6,7 +7,6 @@ class Object
#
# Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.
def to_query(key)
- require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
end
end
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
new file mode 100644
index 0000000000..fec8f7c0ec
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -0,0 +1,47 @@
+module SecureRandom
+ UUID_DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ UUID_URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ UUID_OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ UUID_X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+
+ # Generates a v5 non-random UUID (Universally Unique IDentifier).
+ #
+ # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
+ # ::uuid_from_hash always generates the same UUID for a given name and namespace combination.
+ #
+ # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt
+ def self.uuid_from_hash(hash_class, uuid_namespace, name)
+ if hash_class == Digest::MD5
+ version = 3
+ elsif hash_class == Digest::SHA1
+ version = 5
+ else
+ raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
+ end
+
+ hash = hash_class.new
+ hash.update(uuid_namespace)
+ hash.update(name)
+
+ ary = hash.digest.unpack('NnnnnN')
+ ary[2] = (ary[2] & 0x0FFF) | (version << 12)
+ ary[3] = (ary[3] & 0x3FFF) | 0x8000
+
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
+ end
+
+ # Convenience method for ::uuid_from_hash using Digest::MD5.
+ def self.uuid_v3(uuid_namespace, name)
+ self.uuid_from_hash(Digest::MD5, uuid_namespace, name)
+ end
+
+ # Convenience method for ::uuid_from_hash using Digest::SHA1.
+ def self.uuid_v5(uuid_namespace, name)
+ self.uuid_from_hash(Digest::SHA1, uuid_namespace, name)
+ end
+
+ class << self
+ # Alias for ::uuid.
+ alias_method :uuid_v4, :uuid
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 6018fd9641..ebd0dd3fc7 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -64,7 +64,7 @@ class String
# Returns the first character. If a limit is supplied, returns a substring
# from the beginning of the string until it reaches the limit value. If the
- # given limit is greater than or equal to the string length, returns self.
+ # given limit is greater than or equal to the string length, returns a copy of self.
#
# str = "hello"
# str.first # => "h"
@@ -76,7 +76,7 @@ class String
if limit == 0
''
elsif limit >= size
- self
+ self.dup
else
to(limit - 1)
end
@@ -84,7 +84,7 @@ class String
# Returns the last character of the string. If a limit is supplied, returns a substring
# from the end of the string until it reaches the limit value (counting backwards). If
- # the given limit is greater than or equal to the string length, returns self.
+ # the given limit is greater than or equal to the string length, returns a copy of self.
#
# str = "hello"
# str.last # => "o"
@@ -96,7 +96,7 @@ class String
if limit == 0
''
elsif limit >= size
- self
+ self.dup
else
from(-limit)
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index cf9b1a4ec0..a943752f17 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -31,7 +31,7 @@ class String
def pluralize(count = nil, locale = :en)
locale = count if count.is_a?(Symbol)
if count == 1
- self
+ self.dup
else
ActiveSupport::Inflector.pluralize(self, locale)
end
@@ -130,6 +130,8 @@ class String
#
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
# 'Inflections'.demodulize # => "Inflections"
+ # '::Inflections'.demodulize # => "Inflections"
+ # ''.demodulize # => ''
#
# See also +deconstantize+.
def demodulize
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
index ac1ffa4128..4cd6634558 100644
--- a/activesupport/lib/active_support/core_ext/thread.rb
+++ b/activesupport/lib/active_support/core_ext/thread.rb
@@ -62,6 +62,13 @@ class Thread
_locals.has_key?(key.to_sym)
end
+ # Freezes the thread so that thread local variables cannot be set via
+ # Thread#thread_variable_set, nor can fiber local variables be set.
+ #
+ # me = Thread.current
+ # me.freeze
+ # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals
+ # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals
def freeze
_locals.freeze
super
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 9fd26156c7..dbf1f2f373 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -20,7 +20,7 @@ class Time
:iso8601 => lambda { |time| time.iso8601 }
}
- # Converts to a formatted string. See DATE_FORMATS for builtin formats.
+ # Converts to a formatted string. See DATE_FORMATS for built-in formats.
#
# This method is aliased to <tt>to_s</tt>.
#
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 7df4857c25..09eb732ef7 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -49,6 +49,10 @@ module ActiveSupport
end
end
+ def eql?(other)
+ other.is_a?(Duration) && self == other
+ end
+
def self.===(other) #:nodoc:
other.is_a?(Duration)
rescue ::NoMethodError
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index a270c4452f..51720d0192 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -99,26 +99,46 @@ module ActiveSupport
word
end
- # Capitalizes the first word, turns underscores into spaces, and strips a
- # trailing '_id' if present.
- # Like +titleize+, this is meant for creating pretty output.
+ # Tweaks an attribute name for display to end users.
+ #
+ # Specifically, +humanize+ performs these transformations:
+ #
+ # * Applies human inflection rules to the argument.
+ # * Deletes leading underscores, if any.
+ # * Removes a "_id" suffix if present.
+ # * Replaces underscores with spaces, if any.
+ # * Downcases all words except acronyms.
+ # * Capitalizes the first word.
#
# The capitalization of the first word can be turned off by setting the
- # optional parameter +capitalize+ to false.
- # By default, this parameter is true.
+ # +:capitalize+ option to false (default is true).
#
# humanize('employee_salary') # => "Employee salary"
# humanize('author_id') # => "Author"
# humanize('author_id', capitalize: false) # => "author"
+ # humanize('_id') # => "Id"
+ #
+ # If "SSL" was defined to be an acronym:
+ #
+ # humanize('ssl_error') # => "SSL error"
+ #
def humanize(lower_case_and_underscored_word, options = {})
result = lower_case_and_underscored_word.to_s.dup
+
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.gsub!(/_id$/, "")
+
+ result.sub!(/\A_+/, '')
+ result.sub!(/_id\z/, '')
result.tr!('_', ' ')
- result.gsub!(/([a-z\d]*)/i) { |match|
+
+ result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
- }
- result.gsub!(/^\w/) { |match| match.upcase } if options.fetch(:capitalize, true)
+ end
+
+ if options.fetch(:capitalize, true)
+ result.sub!(/\A\w/) { |match| match.upcase }
+ end
+
result
end
@@ -155,7 +175,7 @@ module ActiveSupport
#
# Singular names are not handled correctly:
#
- # 'business'.classify # => "Busines"
+ # 'calculus'.classify # => "Calculu"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
@@ -172,6 +192,8 @@ module ActiveSupport
#
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
# 'Inflections'.demodulize # => "Inflections"
+ # '::Inflections'.demodulize # => "Inflections"
+ # ''.demodulize # => ""
#
# See also +deconstantize+.
def demodulize(path)
@@ -228,7 +250,7 @@ module ActiveSupport
def constantize(camel_cased_word)
names = camel_cased_word.split('::')
- # Trigger a builtin NameError exception including the ill-formed constant in the message.
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
# Remove the first blank element in case of '::ClassName' notation.
@@ -242,8 +264,8 @@ module ActiveSupport
next candidate if constant.const_defined?(name, false)
next candidate unless Object.const_defined?(name)
- # Go down the ancestors to check it it's owned
- # directly before we reach Object or the end of ancestors.
+ # Go down the ancestors to check if it is owned directly. The check
+ # stops when we reach Object or the end of ancestors tree.
constant = constant.ancestors.inject do |const, ancestor|
break const if ancestor == Object
break ancestor if ancestor.const_defined?(name, false)
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index 6405afc9a6..d85cc086d7 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -13,7 +13,9 @@ module ActiveSupport
def parts
left, right = number.to_s.split('.')
- left.gsub!(DELIMITED_REGEX) { "#{$1}#{options[:delimiter]}" }
+ left.gsub!(DELIMITED_REGEX) do |digit_to_delimit|
+ "#{digit_to_delimit}#{options[:delimiter]}"
+ end
[left, right].compact
end
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 4b9b48539f..98be78b41b 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -64,12 +64,21 @@ module ActiveSupport
def add_event_subscriber(event)
return if %w{ start finish }.include?(event.to_s)
- notifier.subscribe("#{event}.#{namespace}", subscriber)
+ pattern = "#{event}.#{namespace}"
+
+ # don't add multiple subscribers (eg. if methods are redefined)
+ return if subscriber.patterns.include?(pattern)
+
+ subscriber.patterns << pattern
+ notifier.subscribe(pattern, subscriber)
end
end
+ attr_reader :patterns # :nodoc:
+
def initialize
@queue_key = [self.class.name, object_id].join "-"
+ @patterns = []
super
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index c25c97cfa8..f2a2f3c3db 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -185,8 +185,11 @@ module ActiveSupport
end
alias_method :rfc822, :rfc2822
- # <tt>:db</tt> format outputs time in UTC; all others output time in local.
- # Uses TimeWithZone's +strftime+, so <tt>%Z</tt> and <tt>%z</tt> work correctly.
+ # Returns a string of the object's date and time.
+ # Accepts an optional <tt>format</tt>:
+ # * <tt>:default</tt> - default value, mimics Ruby 1.9 Time#to_s format.
+ # * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db).
+ # * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb.
def to_s(format = :default)
if format == :db
utc.to_s(format)
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 38f0d268f4..ee62523824 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -188,16 +188,72 @@ module ActiveSupport
@lazy_zones_map = ThreadSafe::Cache.new
- # Assumes self represents an offset from UTC in seconds (as returned from
- # Time#utc_offset) and turns this into an +HH:MM formatted string.
- #
- # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
- def self.seconds_to_utc_offset(seconds, colon = true)
- format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
- sign = (seconds < 0 ? '-' : '+')
- hours = seconds.abs / 3600
- minutes = (seconds.abs % 3600) / 60
- format % [sign, hours, minutes]
+ class << self
+ # Assumes self represents an offset from UTC in seconds (as returned from
+ # Time#utc_offset) and turns this into an +HH:MM formatted string.
+ #
+ # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
+ def seconds_to_utc_offset(seconds, colon = true)
+ format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
+ sign = (seconds < 0 ? '-' : '+')
+ hours = seconds.abs / 3600
+ minutes = (seconds.abs % 3600) / 60
+ format % [sign, hours, minutes]
+ end
+
+ def find_tzinfo(name)
+ TZInfo::TimezoneProxy.new(MAPPING[name] || name)
+ end
+
+ alias_method :create, :new
+
+ # Returns a TimeZone instance with the given name, or +nil+ if no
+ # such TimeZone instance exists. (This exists to support the use of
+ # this class with the +composed_of+ macro.)
+ def new(name)
+ self[name]
+ end
+
+ # Returns an array of all TimeZone objects. There are multiple
+ # TimeZone objects per time zone, in many cases, to make it easier
+ # for users to find their own time zone.
+ def all
+ @zones ||= zones_map.values.sort
+ end
+
+ def zones_map
+ @zones_map ||= begin
+ MAPPING.each_key {|place| self[place]} # load all the zones
+ @lazy_zones_map
+ end
+ end
+
+ # Locate a specific time zone object. If the argument is a string, it
+ # is interpreted to mean the name of the timezone to locate. If it is a
+ # numeric value it is either the hour offset, or the second offset, of the
+ # timezone to find. (The first one with that offset will be returned.)
+ # Returns +nil+ if no such time zone is known to the system.
+ def [](arg)
+ case arg
+ when String
+ begin
+ @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset }
+ rescue TZInfo::InvalidTimezoneIdentifier
+ nil
+ end
+ when Numeric, ActiveSupport::Duration
+ arg *= 3600 if arg.abs <= 13
+ all.find { |z| z.utc_offset == arg.to_i }
+ else
+ raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
+ end
+ end
+
+ # A convenience method for returning a collection of TimeZone objects
+ # for time zones in the USA.
+ def us_zones
+ @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
+ end
end
include Comparable
@@ -282,6 +338,11 @@ module ActiveSupport
#
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
+ #
+ # However, if the date component is not provided, but any other upper
+ # components are supplied, then the day of the month defaults to 1:
+ #
+ # Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
def parse(str, now=now())
parts = Date._parse(str, false)
return if parts.empty?
@@ -289,7 +350,7 @@ module ActiveSupport
time = Time.new(
parts.fetch(:year, now.year),
parts.fetch(:mon, now.month),
- parts.fetch(:mday, now.day),
+ parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
parts.fetch(:hour, 0),
parts.fetch(:min, 0),
parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
@@ -356,66 +417,9 @@ module ActiveSupport
tzinfo.periods_for_local(time)
end
- def self.find_tzinfo(name)
- TZInfo::TimezoneProxy.new(MAPPING[name] || name)
- end
-
- class << self
- alias_method :create, :new
-
- # Returns a TimeZone instance with the given name, or +nil+ if no
- # such TimeZone instance exists. (This exists to support the use of
- # this class with the +composed_of+ macro.)
- def new(name)
- self[name]
- end
-
- # Returns an array of all TimeZone objects. There are multiple
- # TimeZone objects per time zone, in many cases, to make it easier
- # for users to find their own time zone.
- def all
- @zones ||= zones_map.values.sort
- end
-
- def zones_map
- @zones_map ||= begin
- MAPPING.each_key {|place| self[place]} # load all the zones
- @lazy_zones_map
- end
- end
-
- # Locate a specific time zone object. If the argument is a string, it
- # is interpreted to mean the name of the timezone to locate. If it is a
- # numeric value it is either the hour offset, or the second offset, of the
- # timezone to find. (The first one with that offset will be returned.)
- # Returns +nil+ if no such time zone is known to the system.
- def [](arg)
- case arg
- when String
- begin
- @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset }
- rescue TZInfo::InvalidTimezoneIdentifier
- nil
- end
- when Numeric, ActiveSupport::Duration
- arg *= 3600 if arg.abs <= 13
- all.find { |z| z.utc_offset == arg.to_i }
- else
- raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
- end
- end
-
- # A convenience method for returning a collection of TimeZone objects
- # for time zones in the USA.
- def us_zones
- @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
- end
- end
-
private
-
- def time_now
- Time.now
- end
+ def time_now
+ Time.now
+ end
end
end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 57722fd52a..e0e54f47e4 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -1,10 +1,9 @@
require 'abstract_unit'
require 'active_support/core_ext/array'
require 'active_support/core_ext/big_decimal'
+require 'active_support/core_ext/hash'
require 'active_support/core_ext/object/conversions'
-
-require 'active_support/core_ext' # FIXME: pulling in all to_xml extensions
-require 'active_support/hash_with_indifferent_access'
+require 'active_support/core_ext/string'
class ArrayExtAccessTests < ActiveSupport::TestCase
def test_from
@@ -234,7 +233,7 @@ class ArraySplitTests < ActiveSupport::TestCase
end
class ArrayToXmlTests < ActiveSupport::TestCase
- def test_to_xml
+ def test_to_xml_with_hash_elements
xml = [
{ :name => "David", :age => 26, :age_in_millis => 820497600000 },
{ :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
@@ -249,6 +248,22 @@ class ArrayToXmlTests < ActiveSupport::TestCase
assert xml.include?(%(<name>Jason</name>)), xml
end
+ def test_to_xml_with_non_hash_elements
+ xml = [1, 2, 3].to_xml(:skip_instruct => true, :indent => 0)
+
+ assert_equal '<fixnums type="array"><fixnum', xml.first(29)
+ assert xml.include?(%(<fixnum type="integer">2</fixnum>)), xml
+ end
+
+ def test_to_xml_with_non_hash_different_type_elements
+ xml = [1, 2.0, '3'].to_xml(:skip_instruct => true, :indent => 0)
+
+ assert_equal '<objects type="array"><object', xml.first(29)
+ assert xml.include?(%(<object type="integer">1</object>)), xml
+ assert xml.include?(%(<object type="float">2.0</object>)), xml
+ assert xml.include?(%(object>3</object>)), xml
+ end
+
def test_to_xml_with_dedicated_name
xml = [
{ :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31 }
@@ -269,6 +284,18 @@ class ArrayToXmlTests < ActiveSupport::TestCase
assert xml.include?(%(<name>Jason</name>))
end
+ def test_to_xml_with_indent_set
+ xml = [
+ { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
+ ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 4)
+
+ assert_equal "<objects>\n <object>", xml.first(22)
+ assert xml.include?(%(\n <street-address>Paulina</street-address>))
+ assert xml.include?(%(\n <name>David</name>))
+ assert xml.include?(%(\n <street-address>Evergreen</street-address>))
+ assert xml.include?(%(\n <name>Jason</name>))
+ end
+
def test_to_xml_with_dasherize_false
xml = [
{ :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
@@ -289,7 +316,7 @@ class ArrayToXmlTests < ActiveSupport::TestCase
assert xml.include?(%(<street-address>Evergreen</street-address>))
end
- def test_to_with_instruct
+ def test_to_xml_with_instruct
xml = [
{ :name => "David", :age => 26, :age_in_millis => 820497600000 },
{ :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 28ba33331e..c8f17f4618 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -31,6 +31,13 @@ class DurationTest < ActiveSupport::TestCase
assert !(1.day == 'foo')
end
+ def test_eql
+ assert 1.minute.eql?(1.minute)
+ assert 2.days.eql?(48.hours)
+ assert !1.second.eql?(1)
+ assert !1.eql?(1.second)
+ end
+
def test_inspect
assert_equal '0 seconds', 0.seconds.inspect
assert_equal '1 month', 1.month.inspect
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 69a380c7cb..cb706d77c2 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -46,6 +46,10 @@ class HashExtTest < ActiveSupport::TestCase
@nested_illegal_symbols = { [] => { [] => 3} }
@upcase_strings = { 'A' => 1, 'B' => 2 }
@nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } }
+ @string_array_of_hashes = { 'a' => [ { 'b' => 2 }, { 'c' => 3 }, 4 ] }
+ @symbol_array_of_hashes = { :a => [ { :b => 2 }, { :c => 3 }, 4 ] }
+ @mixed_array_of_hashes = { :a => [ { :b => 2 }, { 'c' => 3 }, 4 ] }
+ @upcase_array_of_hashes = { 'A' => [ { 'B' => 2 }, { 'C' => 3 }, 4 ] }
end
def test_methods
@@ -84,6 +88,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_transform_keys{ |key| key.to_s.upcase }
end
def test_deep_transform_keys_not_mutates
@@ -109,6 +116,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @string_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
end
def test_deep_transform_keys_with_bang_mutates
@@ -134,6 +144,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys
assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys
assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys
+ assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_symbolize_keys
+ assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_symbolize_keys
+ assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_symbolize_keys
end
def test_deep_symbolize_keys_not_mutates
@@ -159,6 +172,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys!
assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys!
assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys!
+ assert_equal @symbol_array_of_hashes, @string_array_of_hashes.deep_dup.deep_symbolize_keys!
+ assert_equal @symbol_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_symbolize_keys!
+ assert_equal @symbol_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_symbolize_keys!
end
def test_deep_symbolize_keys_with_bang_mutates
@@ -204,6 +220,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_strings, @nested_symbols.deep_stringify_keys
assert_equal @nested_strings, @nested_strings.deep_stringify_keys
assert_equal @nested_strings, @nested_mixed.deep_stringify_keys
+ assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_stringify_keys
+ assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_stringify_keys
+ assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_stringify_keys
end
def test_deep_stringify_keys_not_mutates
@@ -229,6 +248,9 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys!
assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys!
assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys!
+ assert_equal @string_array_of_hashes, @string_array_of_hashes.deep_dup.deep_stringify_keys!
+ assert_equal @string_array_of_hashes, @symbol_array_of_hashes.deep_dup.deep_stringify_keys!
+ assert_equal @string_array_of_hashes, @mixed_array_of_hashes.deep_dup.deep_stringify_keys!
end
def test_deep_stringify_keys_with_bang_mutates
@@ -635,7 +657,7 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 1, h['first']
end
- def test_indifferent_subhashes
+ def test_indifferent_sub_hashes
h = {'user' => {'id' => 5}}.with_indifferent_access
['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
@@ -697,6 +719,16 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, hash_1
end
+ def test_deep_merge_with_falsey_values
+ hash_1 = { e: false }
+ hash_2 = { e: 'e' }
+ expected = { e: [:e, false, 'e'] }
+ assert_equal(expected, hash_1.deep_merge(hash_2) { |k, o, n| [k, o, n] })
+
+ hash_1.deep_merge!(hash_2) { |k, o, n| [k, o, n] }
+ assert_equal expected, hash_1
+ end
+
def test_deep_merge_on_indifferent_access
hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } })
hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } })
@@ -905,11 +937,11 @@ class HashExtTest < ActiveSupport::TestCase
def test_compact
hash_contain_nil_value = @symbols.merge(z: nil)
hash_with_only_nil_values = { a: nil, b: nil }
-
+
h = hash_contain_nil_value.dup
assert_equal(@symbols, h.compact)
assert_equal(hash_contain_nil_value, h)
-
+
h = hash_with_only_nil_values.dup
assert_equal({}, h.compact)
assert_equal(hash_with_only_nil_values, h)
@@ -918,11 +950,11 @@ class HashExtTest < ActiveSupport::TestCase
def test_compact!
hash_contain_nil_value = @symbols.merge(z: nil)
hash_with_only_nil_values = { a: nil, b: nil }
-
+
h = hash_contain_nil_value.dup
assert_equal(@symbols, h.compact!)
assert_equal(@symbols, h)
-
+
h = hash_with_only_nil_values.dup
assert_equal({}, h.compact!)
assert_equal({}, h)
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index 18b251173f..d8bf81d02b 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -137,4 +137,4 @@ class KernelDebuggerTest < ActiveSupport::TestCase
ensure
Object.send(:remove_const, :Rails)
end
-end
+end if RUBY_VERSION < '2.0.0'
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index ff6e21854e..380f5ad42b 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -72,7 +72,7 @@ Product = Struct.new(:name) do
def type
@type ||= begin
- nil.type_name
+ :thing_without_same_method_name_as_delegated.name
end
end
end
diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 91d558dbb5..91d558dbb5 100644
--- a/activesupport/test/core_ext/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index e0566e012c..84512380cf 100644
--- a/activesupport/test/core_ext/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -5,34 +5,27 @@ require 'active_support/core_ext/numeric/time'
class DuplicableTest < ActiveSupport::TestCase
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds]
- YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
- NO = []
+ ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
+ # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
+ # raises TypeError exception. Checking here on the runtime whether BigDecimal
+ # will allow dup or not.
begin
bd = BigDecimal.new('4.56')
- YES << bd.dup
+ ALLOW_DUP << bd.dup
rescue TypeError
RAISE_DUP << bd
end
-
def test_duplicable
- (RAISE_DUP + NO).each do |v|
+ RAISE_DUP.each do |v|
assert !v.duplicable?
+ assert_raises(TypeError, v.class.name) { v.dup }
end
- YES.each do |v|
- assert v.duplicable?, "#{v.class} should be duplicable"
- end
-
- (YES + NO).each do |v|
+ ALLOW_DUP.each do |v|
+ assert v.duplicable?, "#{ v.class } should be duplicable"
assert_nothing_raised { v.dup }
end
-
- RAISE_DUP.each do |v|
- assert_raises(TypeError, v.class.name) do
- v.dup
- end
- end
end
end
diff --git a/activesupport/test/core_ext/object/json_test.rb b/activesupport/test/core_ext/object/json_test.rb
deleted file mode 100644
index d3d31530df..0000000000
--- a/activesupport/test/core_ext/object/json_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'abstract_unit'
-
-class JsonTest < ActiveSupport::TestCase
- # See activesupport/test/json/encoding_test.rb for JSON encoding tests
-
- def test_deprecated_require_to_json_rb
- assert_deprecated { require 'active_support/core_ext/object/to_json' }
- end
-end
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index f887a9e613..7457c4655a 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -49,13 +49,15 @@ class ToQueryTest < ActiveSupport::TestCase
def test_nested_empty_hash
assert_equal '',
{}.to_query
- assert_query_equal 'a=1&b%5Bc%5D=3&b%5Bd%5D=',
+ assert_query_equal 'a=1&b%5Bc%5D=3',
{ a: 1, b: { c: 3, d: {} } }
+ assert_query_equal '',
+ { a: {b: {c: {}}} }
assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12',
{ p: 12, b: { c: false, e: nil, f: '' } }
- assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=&b%5Bk%5D=',
+ assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=',
{ b: { c: 3, k: {}, f: '' } }
- assert_query_equal 'a%5B%5D=&b=3',
+ assert_query_equal 'b=3',
{a: [], b: 3}
end
diff --git a/activesupport/test/core_ext/securerandom_test.rb b/activesupport/test/core_ext/securerandom_test.rb
new file mode 100644
index 0000000000..71980f6910
--- /dev/null
+++ b/activesupport/test/core_ext/securerandom_test.rb
@@ -0,0 +1,28 @@
+require 'abstract_unit'
+require 'active_support/core_ext/securerandom'
+
+class SecureRandomExt < ActiveSupport::TestCase
+ def test_v3_uuids
+ assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", SecureRandom.uuid_v3(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", SecureRandom.uuid_v3(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", SecureRandom.uuid_v3(SecureRandom::UUID_OID_NAMESPACE, "1.2.3")
+ assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", SecureRandom.uuid_v3(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_v5_uuids
+ assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", SecureRandom.uuid_v5(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", SecureRandom.uuid_v5(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, "1.2.3")
+ assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", SecureRandom.uuid_v5(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_uuid_v4_alias
+ assert_equal SecureRandom.method(:uuid_v4), SecureRandom.method(:uuid)
+ end
+
+ def test_invalid_hash_class
+ assert_raise ArgumentError do
+ SecureRandom.uuid_from_hash(Digest::SHA2, SecureRandom::UUID_OID_NAMESPACE, '1.2.3')
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index ea12f1ced5..95df173880 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -58,6 +58,11 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal("blargles", "blargle".pluralize(2))
end
+ test 'pluralize with count = 1 still returns new string' do
+ name = "Kuldeep"
+ assert_not_same name.pluralize(1), name
+ end
+
def test_singularize
SingularToPlural.each do |singular, plural|
assert_equal(singular, plural.singularize)
@@ -296,6 +301,12 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.first(4)
end
+ test "#first with Fixnum >= string length still returns a new string" do
+ string = "hello"
+ different_string = string.first(5)
+ assert_not_same different_string, string
+ end
+
test "#last returns the last character" do
assert_equal "o", "hello".last
assert_equal 'x', 'x'.last
@@ -308,6 +319,12 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.last(4)
end
+ test "#last with Fixnum >= string length still returns a new string" do
+ string = "hello"
+ different_string = string.last(5)
+ assert_not_same different_string, string
+ end
+
test "access returns a real string" do
hash = {}
hash["h"] = true
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 35967ba656..b0b4738eb3 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -200,6 +200,7 @@ class InflectorTest < ActiveSupport::TestCase
def test_demodulize
assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account")
assert_equal "Account", ActiveSupport::Inflector.demodulize("Account")
+ assert_equal "Account", ActiveSupport::Inflector.demodulize("::Account")
assert_equal "", ActiveSupport::Inflector.demodulize("")
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index dd03a61176..b556da0046 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -208,9 +208,11 @@ module InflectorTestCases
}
UnderscoreToHuman = {
- "employee_salary" => "Employee salary",
- "employee_id" => "Employee",
- "underground" => "Underground"
+ 'employee_salary' => 'Employee salary',
+ 'employee_id' => 'Employee',
+ 'underground' => 'Underground',
+ '_id' => 'Id',
+ '_external_id' => 'External'
}
UnderscoreToHumanWithoutCapitalize = {
diff --git a/activesupport/test/multibyte_conformance.rb b/activesupport/test/multibyte_conformance_test.rb
index 2baf724da4..6ab8fa28ee 100644
--- a/activesupport/test/multibyte_conformance.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -20,8 +20,8 @@ class Downloader
target.write l
end
end
- end
- end
+ end
+ end
end
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 9bdb92024e..a7a0ae02e7 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/number_helper'
+require 'active_support/core_ext/string/output_safety'
module ActiveSupport
module NumberHelper
@@ -97,6 +98,7 @@ module ActiveSupport
assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901))
assert_equal("0.78901", number_helper.number_to_delimited(0.78901))
assert_equal("123,456.78", number_helper.number_to_delimited("123456.78"))
+ assert_equal("123,456.78", number_helper.number_to_delimited("123456.78".html_safe))
end
end
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
index 253411aa3d..8be8c51f07 100644
--- a/activesupport/test/subscriber_test.rb
+++ b/activesupport/test/subscriber_test.rb
@@ -4,20 +4,27 @@ require 'active_support/subscriber'
class TestSubscriber < ActiveSupport::Subscriber
attach_to :doodle
- cattr_reader :event
+ cattr_reader :events
def self.clear
- @@event = nil
+ @@events = []
end
def open_party(event)
- @@event = event
+ events << event
end
private
def private_party(event)
- @@event = event
+ events << event
+ end
+end
+
+# Monkey patch subscriber to test that only one subscriber per method is added.
+class TestSubscriber
+ def open_party(event)
+ events << event
end
end
@@ -29,12 +36,18 @@ class SubscriberTest < ActiveSupport::TestCase
def test_attaches_subscribers
ActiveSupport::Notifications.instrument("open_party.doodle")
- assert_equal "open_party.doodle", TestSubscriber.event.name
+ assert_equal "open_party.doodle", TestSubscriber.events.first.name
+ end
+
+ def test_attaches_only_one_subscriber
+ ActiveSupport::Notifications.instrument("open_party.doodle")
+
+ assert_equal 1, TestSubscriber.events.size
end
def test_does_not_attach_private_methods
ActiveSupport::Notifications.instrument("private_party.doodle")
- assert_nil TestSubscriber.event
+ assert_equal TestSubscriber.events, []
end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 79ec57af2b..127bcc2b4d 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -254,6 +254,15 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal Time.utc(1999,12,31,19), twz.time
end
+ def test_parse_with_day_omitted
+ with_env_tz 'US/Eastern' do
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ assert_equal Time.local(2000, 2, 1), zone.parse('Feb', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 1), zone.parse('Feb 2005', Time.local(2000, 1, 1))
+ assert_equal Time.local(2005, 2, 2), zone.parse('2 Feb 2005', Time.local(2000, 1, 1))
+ end
+ end
+
def test_parse_should_not_black_out_system_timezone_dst_jump
with_env_tz('EET') do
zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index 753effb54e..f49431cbbf 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -1,6 +1,9 @@
require 'abstract_unit'
require 'active_support/xml_mini'
require 'active_support/builder'
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/hash'
+require 'active_support/core_ext/big_decimal'
module XmlMiniTest
class RenameKeyTest < ActiveSupport::TestCase
@@ -88,6 +91,61 @@ module XmlMiniTest
assert_xml "<b>Howdy</b>"
end
+ test "#to_tag should use the type value in the options hash" do
+ @xml.to_tag(:b, "blue", @options.merge(type: 'color'))
+ assert_xml( "<b type=\"color\">blue</b>" )
+ end
+
+ test "#to_tag accepts symbol types" do
+ @xml.to_tag(:b, :name, @options)
+ assert_xml( "<b type=\"symbol\">name</b>" )
+ end
+
+ test "#to_tag accepts boolean types" do
+ @xml.to_tag(:b, true, @options)
+ assert_xml( "<b type=\"boolean\">true</b>")
+ end
+
+ test "#to_tag accepts float types" do
+ @xml.to_tag(:b, 3.14, @options)
+ assert_xml( "<b type=\"float\">3.14</b>")
+ end
+
+ test "#to_tag accepts decimal types" do
+ @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options)
+ assert_xml( "<b type=\"decimal\">1.2</b>")
+ end
+
+ test "#to_tag accepts date types" do
+ @xml.to_tag(:b, Date.new(2001,2,3), @options)
+ assert_xml( "<b type=\"date\">2001-02-03</b>")
+ end
+
+ test "#to_tag accepts datetime types" do
+ @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,'+7'), @options)
+ assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>")
+ end
+
+ test "#to_tag accepts time types" do
+ @xml.to_tag(:b, Time.new(1993, 02, 24, 12, 0, 0, "+09:00"), @options)
+ assert_xml( "<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>")
+ end
+
+ test "#to_tag accepts array types" do
+ @xml.to_tag(:b, ["first_name", "last_name"], @options)
+ assert_xml( "<b type=\"array\"><b>first_name</b><b>last_name</b></b>" )
+ end
+
+ test "#to_tag accepts hash types" do
+ @xml.to_tag(:b, { first_name: "Bob", last_name: "Marley" }, @options)
+ assert_xml( "<b><first-name>Bob</first-name><last-name>Marley</last-name></b>" )
+ end
+
+ test "#to_tag should not add type when skip types option is set" do
+ @xml.to_tag(:b, "Bob", @options.merge(skip_types: 1))
+ assert_xml( "<b>Bob</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>"
@@ -97,7 +155,6 @@ module XmlMiniTest
@xml.to_tag(:"New York", 33, @options)
assert_xml "<New---York type=\"integer\">33</New---York>"
end
- # TODO: test the remaining functions hidden in #to_tag.
end
class WithBackendTest < ActiveSupport::TestCase
diff --git a/ci/travis.rb b/ci/travis.rb
index 7e68993332..956a01dbee 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -52,7 +52,7 @@ class Build
def tasks
if activerecord?
- ['mysql:rebuild_databases', "#{adapter}:#{'isolated_' if isolated?}test"]
+ ['db:mysql:rebuild', "#{adapter}:#{'isolated_' if isolated?}test"]
else
["test#{':isolated' if isolated?}"]
end
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 246e1d3b96..14ad4fd424 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Updates the maintenance policy to match the latest versions of Rails
+
+ *Matias Korhonen*
+
* Switched the order of `Applying a default scope` and `Merging of scopes` subsections so default scopes are introduced first.
*Alex Riabov*
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 898f9ff05b..318a1ef1c7 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -381,9 +381,12 @@ a, a:link, a:visited {
font: inherit;
padding-left: .75em;
font-size: .95em;
- background-position: 96% -65px;
+ background-position: 96% 16px;
-webkit-appearance: none;
}
+ .guides-index-small .guides-index-item:hover{
+ background-position: 96% -65px;
+ }
}
#guides {
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index c3d7e96c4d..091a87aa4c 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -3,7 +3,7 @@ source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.1.0'
-# Use sqlite3 as the database for Active Record
+# Use SQLite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.1'
@@ -14,7 +14,7 @@ gem 'coffee-rails', '~> 4.0.0'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
-# Use jquery as the JavaScript library
+# Use jQuery as the JavaScript library
gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
@@ -29,7 +29,7 @@ gem 'spring', group: :development
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
-# Use unicorn as the app server
+# Use Unicorn as the app server
# gem 'unicorn'
# Use Capistrano for deployment
diff --git a/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile
index 05de8bb536..ba6b733dd2 100644
--- a/guides/code/getting_started/Rakefile
+++ b/guides/code/getting_started/Rakefile
@@ -3,4 +3,4 @@
require File.expand_path('../config/application', __FILE__)
-Blog::Application.load_tasks
+Rails.application.load_tasks
diff --git a/guides/code/getting_started/config.ru b/guides/code/getting_started/config.ru
index ddf869e921..5bc2a619e8 100644
--- a/guides/code/getting_started/config.ru
+++ b/guides/code/getting_started/config.ru
@@ -1,4 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
-run Blog::Application
+run Rails.application
diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb
index e7e341c960..ee8d90dc65 100644
--- a/guides/code/getting_started/config/environment.rb
+++ b/guides/code/getting_started/config/environment.rb
@@ -2,4 +2,4 @@
require File.expand_path('../application', __FILE__)
# Initialize the Rails application.
-Blog::Application.initialize!
+Rails.application.initialize!
diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb
index 7e5692b08b..5c1c600feb 100644
--- a/guides/code/getting_started/config/environments/development.rb
+++ b/guides/code/getting_started/config/environments/development.rb
@@ -1,4 +1,4 @@
-Blog::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
@@ -27,4 +27,12 @@ Blog::Application.configure do
# Debug mode disables concatenation and preprocessing of assets.
config.assets.debug = true
+
+ # Generate digests for assets URLs.
+ config.assets.digest = true
+
+ # Adds additional error checking when serving assets at runtime.
+ # Checks for improperly declared sprockets dependencies.
+ # Raises helpful error messages.
+ config.assets.raise_runtime_errors = true
end
diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb
index 8c514e065e..c8ae858574 100644
--- a/guides/code/getting_started/config/environments/production.rb
+++ b/guides/code/getting_started/config/environments/production.rb
@@ -1,4 +1,4 @@
-Blog::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
diff --git a/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb
index 34ab1530d1..680d0b9e06 100644
--- a/guides/code/getting_started/config/environments/test.rb
+++ b/guides/code/getting_started/config/environments/test.rb
@@ -1,4 +1,4 @@
-Blog::Application.configure do
+Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb
index aaf57731be..c2a549c299 100644
--- a/guides/code/getting_started/config/initializers/secret_token.rb
+++ b/guides/code/getting_started/config/initializers/secret_token.rb
@@ -9,4 +9,4 @@
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
-Blog::Application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53'
+Rails.application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53'
diff --git a/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb
index 3b2ca93ab9..1b9fa324d4 100644
--- a/guides/code/getting_started/config/initializers/session_store.rb
+++ b/guides/code/getting_started/config/initializers/session_store.rb
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Blog::Application.config.session_store :cookie_store, key: '_blog_session'
+Rails.application.config.session_store :cookie_store, key: '_blog_session'
diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb
index 0155b613a3..65d273b58d 100644
--- a/guides/code/getting_started/config/routes.rb
+++ b/guides/code/getting_started/config/routes.rb
@@ -1,4 +1,4 @@
-Blog::Application.routes.draw do
+Rails.application.routes.draw do
resources :posts do
resources :comments
end
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index 9d488a8a15..9d1d5567f6 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -24,11 +24,11 @@ begin
require 'redcarpet'
rescue LoadError
# This can happen if doc:guides is executed in an application.
- $stderr.puts('Generating guides requires Redcarpet 2.1.1+.')
+ $stderr.puts('Generating guides requires Redcarpet 3.1.2+.')
$stderr.puts(<<ERROR) if bundler?
Please add
- gem 'redcarpet', '~> 2.1.1'
+ gem 'redcarpet', '~> 3.1.2'
to the Gemfile, run
diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb
index 169453400f..a78c2e9fca 100644
--- a/guides/rails_guides/helpers.rb
+++ b/guides/rails_guides/helpers.rb
@@ -39,7 +39,7 @@ module RailsGuides
def author(name, nick, image = 'credits_pic_blank.gif', &block)
image = "images/#{image}"
- result = content_tag(:img, nil, :src => image, :class => 'left pic', :alt => name, :width => 91, :height => 91)
+ result = tag(:img, :src => image, :class => 'left pic', :alt => name, :width => 91, :height => 91)
result << content_tag(:h3, name)
result << content_tag(:p, capture(&block))
content_tag(:div, result, :class => 'clearfix', :id => nick)
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index 547c6d2c15..dbf7ff311d 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -79,10 +79,10 @@ module RailsGuides
def generate_structure
@headings_for_index = []
if @body.present?
- @body = Nokogiri::HTML(@body).tap do |doc|
+ @body = Nokogiri::HTML.fragment(@body).tap do |doc|
hierarchy = []
- doc.at('body').children.each do |node|
+ doc.children.each do |node|
if node.name =~ /^h[3-6]$/
case node.name
when 'h3'
@@ -116,7 +116,7 @@ module RailsGuides
end
end
- @index = Nokogiri::HTML(engine.render(raw_index)).tap do |doc|
+ @index = Nokogiri::HTML.fragment(engine.render(raw_index)).tap do |doc|
doc.at('ol')[:class] = 'chapters'
end.to_html
@@ -130,8 +130,8 @@ module RailsGuides
end
def generate_title
- if heading = Nokogiri::HTML(@header).at(:h2)
- @title = "#{heading.text} — Ruby on Rails Guides".html_safe
+ if heading = Nokogiri::HTML.fragment(@header).at(:h2)
+ @title = "#{heading.text} — Ruby on Rails Guides"
else
@title = "Ruby on Rails Guides"
end
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 8c633fa169..2302a618b6 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -594,7 +594,7 @@ The internals of the various <code>rake gem</code> tasks have been substantially
* Various files in /public that deal with CGI and FCGI dispatching are no longer generated in every Rails application by default (you can still get them if you need them by adding `--with-dispatchers` when you run the `rails` command, or add them later with `rake rails:update:generate_dispatchers`).
* Rails Guides have been converted from AsciiDoc to Textile markup.
* Scaffolded views and controllers have been cleaned up a bit.
-* `script/server` now accepts a <tt>--path</tt> argument to mount a Rails application from a specific path.
+* `script/server` now accepts a `--path` argument to mount a Rails application from a specific path.
* If any configured gems are missing, the gem rake tasks will skip loading much of the environment. This should solve many of the "chicken-and-egg" problems where rake gems:install couldn't run because gems were missing.
* Gems are now unpacked exactly once. This fixes issues with gems (hoe, for instance) which are packed with read-only permissions on the files.
@@ -618,4 +618,4 @@ A few pieces of older code are deprecated in this release:
Credits
-------
-Release notes compiled by [Mike Gunderloy](http://afreshcup.com.) This version of the Rails 2.3 release notes was compiled based on RC2 of Rails 2.3.
+Release notes compiled by [Mike Gunderloy](http://afreshcup.com). This version of the Rails 2.3 release notes was compiled based on RC2 of Rails 2.3.
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index dd81ec58f9..db34fa401f 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -294,7 +294,7 @@ NOTE. The old style `map` commands still work as before with a backwards compati
Deprecations
* The catch all route for non-REST applications (`/:controller/:action/:id`) is now commented out.
-* Routes :path\_prefix no longer exists and :name\_prefix now automatically adds "\_" at the end of the given value.
+* Routes `:path_prefix` no longer exists and `:name_prefix` now automatically adds "_" at the end of the given value.
More Information:
* [The Rails 3 Router: Rack it Up](http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/)
@@ -608,4 +608,4 @@ Credits
See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails 3. Kudos to all of them.
-Rails 3.0 Release Notes were compiled by [Mikel Lindsaar](http://lindsaar.net.)
+Rails 3.0 Release Notes were compiled by [Mikel Lindsaar](http://lindsaar.net).
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index ce811a583b..cdcde67869 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -187,7 +187,7 @@ Action Pack
Rails will use `layouts/single_car` when a request comes in `:show` action, and use `layouts/application` (or `layouts/cars`, if exists) when a request comes in for any other actions.
-* `form\_for` is changed to use `#{action}\_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}\_#{action}`.
+* `form_for` is changed to use `#{action}_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}_#{action}`.
* `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`.
diff --git a/guides/source/_license.html.erb b/guides/source/_license.html.erb
index 00b4466f50..d22f016948 100644
--- a/guides/source/_license.html.erb
+++ b/guides/source/_license.html.erb
@@ -1,2 +1,2 @@
-<p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</p>
+<p>This work is licensed under a <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International</a> License</p>
<p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p>
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 9fd930963a..6ec3aa78a4 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -10,13 +10,10 @@
</p>
<% else %>
<p>
- These are the new guides for Rails 4.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These are the new guides for Rails 4.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
</p>
<% end %>
<p>
- The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.17/">http://guides.rubyonrails.org/v3.2.17/</a>.
-</p>
-<p>
- The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
+ The guides for earlier releases: <a href="http://guides.rubyonrails.org/v4.1.1/">Rails 4.1.1</a>, <a href="http://guides.rubyonrails.org/v4.0.5/">Rails 4.0.5</a>, <a href="http://guides.rubyonrails.org/v3.2.18/">Rails 3.2.18</a> and <a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>.
</p>
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 0f46ba8698..1735188f27 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -34,7 +34,7 @@ The naming convention of controllers in Rails favors pluralization of the last w
Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and keeps URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details.
-NOTE: The controller naming convention differs from the naming convention of models, which expected to be named in singular form.
+NOTE: The controller naming convention differs from the naming convention of models, which are expected to be named in singular form.
Methods and Actions
@@ -260,7 +260,7 @@ used:
params.require(:log_entry).permit!
```
-This will mark the `:log_entry` parameters hash and any subhash of it
+This will mark the `:log_entry` parameters hash and any sub-hash of it
permitted. Extreme care should be taken when using `permit!` as it
will allow all current and future model attributes to be
mass-assigned.
@@ -364,21 +364,21 @@ If you need a different session storage mechanism, you can change it in the `con
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails g active_record:session_migration")
-# YourApp::Application.config.session_store :active_record_store
+# Rails.application.config.session_store :active_record_store
```
Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in `config/initializers/session_store.rb`:
```ruby
# Be sure to restart your server when you modify this file.
-YourApp::Application.config.session_store :cookie_store, key: '_your_app_session'
+Rails.application.config.session_store :cookie_store, key: '_your_app_session'
```
You can also pass a `:domain` key and specify the domain name for the cookie:
```ruby
# Be sure to restart your server when you modify this file.
-YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
+Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
```
Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml`
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 6dc7fb1606..c67f6188c4 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -30,7 +30,7 @@ views.
#### Create the Mailer
```bash
-$ rails generate mailer UserMailer
+$ bin/rails generate mailer UserMailer
create app/mailers/user_mailer.rb
invoke erb
create app/views/user_mailer
@@ -146,8 +146,8 @@ Setting this up is painfully simple.
First, let's create a simple `User` scaffold:
```bash
-$ rails generate scaffold user name email login
-$ rake db:migrate
+$ bin/rails generate scaffold user name email login
+$ bin/rake db:migrate
```
Now that we have a user model to play with, we will just edit the
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 74f95bfcfd..b700d1c861 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -28,7 +28,7 @@ For each controller there is an associated directory in the `app/views` director
Let's take a look at what Rails does by default when creating a new resource using the scaffold generator:
```bash
-$ rails generate scaffold post
+$ bin/rails generate scaffold post
[...]
invoke scaffold_controller
create app/controllers/posts_controller.rb
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index a184f0753d..e815f6b674 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -309,10 +309,10 @@ 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.
-Validation is a very important issue to consider when persisting to database, so
+Validation is a very important issue to consider when persisting to the database, so
the methods `create`, `save` and `update` take it into account when
running: they return `false` when validation fails and they didn't actually
-perform any operation on database. All of these have a bang counterpart (that
+perform any operation on the database. All of these have a bang counterpart (that
is, `create!`, `save!` and `update!`), which are stricter in that
they raise the exception `ActiveRecord::RecordInvalid` if validation fails.
A quick example to illustrate:
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 667433285f..fbcce325ed 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -92,6 +92,7 @@ Here is a list with all the available Active Record callbacks, listed in the sam
* `around_create`
* `after_create`
* `after_save`
+* `after_commit/after_rollback`
### Updating an Object
@@ -103,12 +104,14 @@ Here is a list with all the available Active Record callbacks, listed in the sam
* `around_update`
* `after_update`
* `after_save`
+* `after_commit/after_rollback`
### Destroying an Object
* `before_destroy`
* `around_destroy`
* `after_destroy`
+* `after_commit/after_rollback`
WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
new file mode 100644
index 0000000000..b69c8dbb7a
--- /dev/null
+++ b/guides/source/active_record_postgresql.md
@@ -0,0 +1,433 @@
+Active Record and PostgreSQL
+============================
+
+This guide covers PostgreSQL specific usage of Active Record.
+
+After reading this guide, you will know:
+
+* How to use PostgreSQL's datatypes.
+* How to use UUID primary keys.
+* How to implement full text search with PostgreSQL.
+
+--------------------------------------------------------------------------------
+
+In order to use the PostgreSQL adapter you need to have at least version 8.2
+installed. Older versions are not supported.
+
+To get started with PostgreSQL have a look at the
+[configuring Rails guide](configuring.html#configuring-a-postgresql-database).
+It describes how to properly setup Active Record for PostgreSQL.
+
+Datatypes
+---------
+
+PostgreSQL offers a number of specific datatypes. Following is a list of types,
+that are supported by the PostgreSQL adapter.
+
+### Bytea
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-binary.html)
+* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-binarystring.html)
+
+```ruby
+# db/migrate/20140207133952_create_documents.rb
+create_table :documents do |t|
+ t.binary 'payload'
+end
+
+# app/models/document.rb
+class Document < ActiveRecord::Base
+end
+
+# Usage
+data = File.read(Rails.root + "tmp/output.pdf")
+Document.create payload: data
+```
+
+### Array
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/arrays.html)
+* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-array.html)
+
+```ruby
+# db/migrate/20140207133952_create_books.rb
+create_table :book do |t|
+ t.string 'title'
+ t.string 'tags', array: true
+ t.integer 'ratings', array: true
+end
+
+# app/models/book.rb
+class Book < ActiveRecord::Base
+end
+
+# Usage
+Book.create title: "Brave New World",
+ tags: ["fantasy", "fiction"],
+ ratings: [4, 5]
+
+## Books for a single tag
+Book.where("'fantasy' = ANY (tags)")
+
+## Books for multiple tags
+Book.where("tags @> ARRAY[?]::varchar[]", ["fantasy", "fiction"])
+
+## Books with 3 or more ratings
+Book.where("array_length(ratings, 1) >= 3")
+```
+
+### Hstore
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/hstore.html)
+
+```ruby
+# db/migrate/20131009135255_create_profiles.rb
+ActiveRecord::Schema.define do
+ create_table :profiles do |t|
+ t.hstore 'settings'
+ end
+end
+
+# app/models/profile.rb
+class Profile < ActiveRecord::Base
+end
+
+# Usage
+Profile.create(settings: { "color" => "blue", "resolution" => "800x600" })
+
+profile = Profile.first
+profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
+
+profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
+profile.save!
+
+## you need to call _will_change! if you are editing the store in place
+profile.settings["color"] = "green"
+profile.settings_will_change!
+profile.save!
+```
+
+### JSON
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-json.html)
+* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-json.html)
+
+```ruby
+# db/migrate/20131220144913_create_events.rb
+create_table :events do |t|
+ t.json 'payload'
+end
+
+# app/models/event.rb
+class Event < ActiveRecord::Base
+end
+
+# Usage
+Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]})
+
+event = Event.first
+event.payload # => {"kind"=>"user_renamed", "change"=>["jack", "john"]}
+
+## Query based on JSON document
+Event.where("payload->'kind' = ?", "user_renamed")
+```
+
+### Range Types
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/rangetypes.html)
+* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-range.html)
+
+This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.1.1/Range.html) objects.
+
+```ruby
+# db/migrate/20130923065404_create_events.rb
+create_table :events do |t|
+ t.daterange 'duration'
+end
+
+# app/models/event.rb
+class Event < ActiveRecord::Base
+end
+
+# Usage
+Event.create(duration: Date.new(2014, 2, 11)..Date.new(2014, 2, 12))
+
+event = Event.first
+event.duration # => Tue, 11 Feb 2014...Thu, 13 Feb 2014
+
+## All Events on a given date
+Event.where("duration @> ?::date", Date.new(2014, 2, 12))
+
+## Working with range bounds
+event = Event.
+ select("lower(duration) AS starts_at").
+ select("upper(duration) AS ends_at").first
+
+event.starts_at # => Tue, 11 Feb 2014
+event.ends_at # => Thu, 13 Feb 2014
+```
+
+### Composite Types
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/rowtypes.html)
+
+Currently there is no special support for composite types. They are mapped to
+normal text columns:
+
+```sql
+CREATE TYPE full_address AS
+(
+ city VARCHAR(90),
+ street VARCHAR(90)
+);
+```
+
+```ruby
+# db/migrate/20140207133952_create_contacts.rb
+execute <<-SQL
+ CREATE TYPE full_address AS
+ (
+ city VARCHAR(90),
+ street VARCHAR(90)
+ );
+SQL
+create_table :contacts do |t|
+ t.column :address, :full_address
+end
+
+# app/models/contact.rb
+class Contact < ActiveRecord::Base
+end
+
+# Usage
+Contact.create address: "(Paris,Champs-Élysées)"
+contact = Contact.first
+contact.address # => "(Paris,Champs-Élysées)"
+contact.address = "(Paris,Rue Basse)"
+contact.save!
+```
+
+### Enumerated Types
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-enum.html)
+
+Currently there is no special support for enumerated types. They are mapped as
+normal text columns:
+
+```ruby
+# db/migrate/20131220144913_create_events.rb
+execute <<-SQL
+ CREATE TYPE article_status AS ENUM ('draft', 'published');
+SQL
+create_table :articles do |t|
+ t.column :status, :article_status
+end
+
+# app/models/article.rb
+class Article < ActiveRecord::Base
+end
+
+# Usage
+Article.create status: "draft"
+article = Article.first
+article.status # => "draft"
+
+article.status = "published"
+article.save!
+```
+
+### UUID
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-uuid.html)
+* [generator functions](http://www.postgresql.org/docs/9.3/static/uuid-ossp.html)
+
+
+```ruby
+# db/migrate/20131220144913_create_revisions.rb
+create_table :revisions do |t|
+ t.column :identifier, :uuid
+end
+
+# app/models/revision.rb
+class Revision < ActiveRecord::Base
+end
+
+# Usage
+Revision.create identifier: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11"
+
+revision = Revision.first
+revision.identifier # => "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
+```
+
+### Bit String Types
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-bit.html)
+* [functions and operators](http://www.postgresql.org/docs/9.3/static/functions-bitstring.html)
+
+```ruby
+# db/migrate/20131220144913_create_users.rb
+create_table :users, force: true do |t|
+ t.column :settings, "bit(8)"
+end
+
+# app/models/device.rb
+class User < ActiveRecord::Base
+end
+
+# Usage
+User.create settings: "01010011"
+user = User.first
+user.settings # => "(Paris,Champs-Élysées)"
+user.settings = "0xAF"
+user.settings # => 10101111
+user.save!
+```
+
+### Network Address Types
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-net-types.html)
+
+The types `inet` and `cidr` are mapped to Ruby
+[`IPAddr`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/ipaddr/rdoc/IPAddr.html)
+objects. The `macaddr` type is mapped to normal text.
+
+```ruby
+# db/migrate/20140508144913_create_devices.rb
+create_table(:devices, force: true) do |t|
+ t.inet 'ip'
+ t.cidr 'network'
+ t.macaddr 'address'
+end
+
+# app/models/device.rb
+class Device < ActiveRecord::Base
+end
+
+# Usage
+macbook = Device.create(ip: "192.168.1.12",
+ network: "192.168.2.0/24",
+ address: "32:01:16:6d:05:ef")
+
+macbook.ip
+# => #<IPAddr: IPv4:192.168.1.12/255.255.255.255>
+
+macbook.network
+# => #<IPAddr: IPv4:192.168.2.0/255.255.255.0>
+
+macbook.address
+# => "32:01:16:6d:05:ef"
+```
+
+### Geometric Types
+
+* [type definition](http://www.postgresql.org/docs/9.3/static/datatype-geometric.html)
+
+All geometric types are mapped to normal text.
+
+
+UUID Primary Keys
+-----------------
+
+NOTE: you need to enable the `uuid-ossp` extension to generate UUIDs.
+
+```ruby
+# db/migrate/20131220144913_create_devices.rb
+enable_extension 'uuid-ossp' unless extension_enabled?('uuid-ossp')
+create_table :devices, id: :uuid, default: 'uuid_generate_v4()' do |t|
+ t.string :kind
+end
+
+# app/models/device.rb
+class Device < ActiveRecord::Base
+end
+
+# Usage
+device = Device.create
+device.id # => "814865cd-5a1d-4771-9306-4268f188fe9e"
+```
+
+Full Text Search
+----------------
+
+```ruby
+# db/migrate/20131220144913_create_documents.rb
+create_table :documents do |t|
+ t.string 'title'
+ t.string 'body'
+end
+
+execute "CREATE INDEX documents_idx ON documents USING gin(to_tsvector('english', title || ' ' || body));"
+
+# app/models/document.rb
+class Document < ActiveRecord::Base
+end
+
+# Usage
+Document.create(title: "Cats and Dogs", body: "are nice!")
+
+## all documents matching 'cat & dog'
+Document.where("to_tsvector('english', title || ' ' || body) @@ to_tsquery(?)",
+ "cat & dog")
+```
+
+Views
+-----
+
+* [view creation](http://www.postgresql.org/docs/9.3/static/sql-createview.html)
+
+Imagine you need to work with a legacy database containing the following table:
+
+```
+rails_pg_guide=# \d "TBL_ART"
+ Table "public.TBL_ART"
+ Column | Type | Modifiers
+------------+-----------------------------+------------------------------------------------------------
+ INT_ID | integer | not null default nextval('"TBL_ART_INT_ID_seq"'::regclass)
+ STR_TITLE | character varying |
+ STR_STAT | character varying | default 'draft'::character varying
+ DT_PUBL_AT | timestamp without time zone |
+ BL_ARCH | boolean | default false
+Indexes:
+ "TBL_ART_pkey" PRIMARY KEY, btree ("INT_ID")
+```
+
+This table does not follow the Rails conventions at all.
+Because simple PostgreSQL views are updateable by default,
+we can wrap it as follows:
+
+```ruby
+# db/migrate/20131220144913_create_articles_view.rb
+execute <<-SQL
+CREATE VIEW articles AS
+ SELECT "INT_ID" AS id,
+ "STR_TITLE" AS title,
+ "STR_STAT" AS status,
+ "DT_PUBL_AT" AS published_at,
+ "BL_ARCH" AS archived
+ FROM "TBL_ART"
+ WHERE "BL_ARCH" = 'f'
+ SQL
+
+# app/models/article.rb
+class Article < ActiveRecord::Base
+ self.primary_key = "id"
+ def archive!
+ update_attribute :archived, true
+ end
+end
+
+# Usage
+first = Article.create! title: "Winter is coming",
+ status: "published",
+ published_at: 1.year.ago
+second = Article.create! title: "Brace yourself",
+ status: "draft",
+ published_at: 1.month.ago
+
+Article.count # => 1
+first.archive!
+p Article.count # => 2
+```
+
+Note: This application only cares about non-archived `Articles`. A view also
+allows for conditions so we can exclude the archived `Articles` directly.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index a483a6dd24..6bedec1e63 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -85,7 +85,7 @@ end
We can see how it works by looking at some `rails console` output:
```ruby
-$ rails console
+$ bin/rails console
>> p = Person.new(name: "John Doe")
=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>
>> p.new_record?
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 5698dc0413..dfe9d30698 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -572,12 +572,12 @@ NOTE: Defined in `active_support/core_ext/module/aliasing.rb`.
#### `alias_attribute`
-Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (my mnemonic is they go in the same order as if you did an assignment):
+Model attributes have a reader, a writer, and a predicate. You can alias a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (one mnemonic is that they go in the same order as if you did an assignment):
```ruby
class User < ActiveRecord::Base
- # let me refer to the email column as "login",
- # possibly meaningful for authentication code
+ # You can refer to the email column as "login".
+ # This can be meaningful for authentication code.
alias_attribute :login, :email
end
```
@@ -761,7 +761,7 @@ Arguments may be bare constant names:
Math.qualified_const_get("E") # => 2.718281828459045
```
-These methods are analogous to their builtin counterparts. In particular,
+These methods are analogous to their built-in counterparts. In particular,
`qualified_constant_defined?` accepts an optional second argument to be
able to say whether you want the predicate to look in the ancestors.
This flag is taken into account for each constant in the expression while
@@ -792,7 +792,7 @@ N.qualified_const_defined?("C::X") # => true
As the last example implies, the second argument defaults to true,
as in `const_defined?`.
-For coherence with the builtin methods only relative paths are accepted.
+For coherence with the built-in methods only relative paths are accepted.
Absolute qualified constant names like `::Math::PI` raise `NameError`.
NOTE: Defined in `active_support/core_ext/module/qualified_const.rb`.
@@ -964,20 +964,7 @@ NOTE: Defined in `active_support/core_ext/module/delegation.rb`
There are cases where you need to define a method with `define_method`, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either.
-The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. Rails uses it in a few places, for instance when it generates an association's API:
-
-```ruby
-redefine_method("#{reflection.name}=") do |new_value|
- association = association_instance_get(reflection.name)
-
- if association.nil? || association.target != new_value
- association = association_proxy_class.new(self, reflection)
- end
-
- association.replace(new_value)
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
-end
-```
+The method `redefine_method` prevents such a potential warning, removing the existing method before if needed.
NOTE: Defined in `active_support/core_ext/module/remove_method.rb`
@@ -1119,7 +1106,7 @@ end
A model may find it useful to set `:instance_accessor` to `false` as a way to prevent mass-assignment from setting the attribute.
-NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`. `active_support/core_ext/class/attribute_accessors.rb` is deprecated and will be removed in Ruby on Rails 4.2.
+NOTE: Defined in `active_support/core_ext/module/attribute_accessors.rb`.
### Subclasses & Descendants
@@ -1644,6 +1631,9 @@ Given a string with a qualified constant name, `demodulize` returns the very con
"Product".demodulize # => "Product"
"Backoffice::UsersController".demodulize # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
+"::Inflections".demodulize # => "Inflections"
+"".demodulize # => ""
+
```
Active Record for example uses this method to compute the name of a counter cache column:
@@ -1778,21 +1768,36 @@ NOTE: Defined in `active_support/core_ext/string/inflections.rb`.
#### `humanize`
-The method `humanize` gives you a sensible name for display out of an attribute name. To do so it replaces underscores with spaces, removes any "_id" suffix, and capitalizes the first word:
+The method `humanize` tweaks an attribute name for display to end users.
+
+Specifically performs these transformations:
+
+ * Applies human inflection rules to the argument.
+ * Deletes leading underscores, if any.
+ * Removes a "_id" suffix if present.
+ * Replaces underscores with spaces, if any.
+ * Downcases all words except acronyms.
+ * Capitalizes the first word.
+
+The capitalization of the first word can be turned off by setting the
++:capitalize+ option to false (default is true).
```ruby
-"name".humanize # => "Name"
-"author_id".humanize # => "Author"
-"comments_count".humanize # => "Comments count"
+"name".humanize # => "Name"
+"author_id".humanize # => "Author"
+"author_id".humanize(capitalize: false) # => "author"
+"comments_count".humanize # => "Comments count"
+"_id".humanize # => "Id"
```
-The capitalization of the first word can be turned off by setting the optional parameter `capitalize` to false:
+If "SSL" was defined to be an acronym:
```ruby
-"author_id".humanize(capitalize: false) # => "author"
+'ssl_error'.humanize # => "SSL error"
```
-The helper method `full_messages` uses `humanize` as a fallback to include attribute names:
+The helper method `full_messages` uses `humanize` as a fallback to include
+attribute names:
```ruby
def full_messages
@@ -2920,7 +2925,7 @@ NOTE: Defined in `active_support/core_ext/hash/indifferent_access.rb`.
### Compacting
-The methods `compact` and `compact!` return a Hash without items with `nil` value.
+The methods `compact` and `compact!` return a Hash without items with `nil` value.
```ruby
{a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2}
@@ -3847,7 +3852,7 @@ rescue NameError => e
end
```
-NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
+NOTE: Defined in `active_support/core_ext/name_error.rb`.
Extensions to `LoadError`
-------------------------
@@ -3870,4 +3875,4 @@ rescue NameError => e
end
```
-NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
+NOTE: Defined in `active_support/core_ext/load_error.rb`.
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 6c77a40d42..d947c5d9d5 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -17,7 +17,7 @@ After reading this guide, you will know:
Introduction to instrumentation
-------------------------------
-The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in <TODO: link to section detailing each hook point>. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
+The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in (TODO: link to section detailing each hook point). With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be **subscribed** to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
@@ -457,6 +457,7 @@ Most times you only care about the data itself. Here is a shortcut to just get t
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
data = args.extract_options!
data # { extra: :information }
+end
```
You may also subscribe to events matching a regular expression. This enables you to subscribe to
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 261538d0be..f01675b50d 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -175,8 +175,8 @@ end
The API is careful not to commit to any particular value, the method has
predicate semantics, that's enough.
-Filenames
----------
+File Names
+----------
As a rule of thumb, use filenames relative to the application root:
@@ -286,3 +286,36 @@ self.class_eval %{
end
}
```
+
+Method Visibility
+-----------------
+
+When writing documentation for Rails, it's important to understand the difference between public user-facing API vs internal API.
+
+Rails, like most libraries, uses the private keyword from Ruby for defining internal API. However, public API follows a slightly different convention. Instead of assuming all public methods are designed for user consumption, Rails uses the `:nodoc:` directive to annotate these kinds of methods as internal API.
+
+This means that there are methods in Rails with `public` visibility that aren't meant for user consumption.
+
+An example of this is `ActiveRecord::Core::ClassMethods#arel_table`:
+
+```ruby
+module ActiveRecord::Core::ClassMethods
+ def arel_table #:nodoc:
+ # do some magic..
+ end
+end
+```
+
+If you thought, "this method looks like a public class method for `ActiveRecord::Core`", you were right. But actually the Rails team doesn't want users to rely on this method. So they mark it as `:nodoc:` and it's removed from public documentation. The reasoning behind this is to allow the team to change these methods according to their internal needs across releases as they see fit. The name of this method could change, or the return value, or this entire class may disappear; there's no guarantee and so you shouldn't depend on this API in your plugins or applications. Otherwise, you risk your app or gem breaking when you upgrade to a newer release of Rails.
+
+As a contributor, it's important to think about whether this API is meant for end-user consumption. The Rails team is committed to not making any breaking changes to public API across releases without going through a full deprecation cycle. It's recommended that you `:nodoc:` any of your internal methods/classes unless they're already private (meaning visibility), in which case it's internal by default. Once the API stabilizes the visibility can change, but changing public API is much harder due to backwards compatibility.
+
+A class or module is marked with `:nodoc:` to indicate that all methods are internal API and should never be used directly.
+
+If you come across an existing `:nodoc:` you should tread lightly. Consider asking someone from the core team or author of the code before removing it. This should almost always happen through a pull request instead of the docrails project.
+
+A `:nodoc:` should never be added simply because a method or class is missing documentation. There may be an instance where an internal public method wasn't given a `:nodoc:` by mistake, for example when switching a method from private to public visibility. When this happens it should be discussed over a PR on a case-by-case basis and never committed directly to docrails.
+
+To summarize, the Rails team uses `:nodoc:` to mark publicly visible methods and classes for internal use; changes to the visibility of API should be considered carefully and discussed over a pull request first.
+
+If you have a question on how the Rails team handles certain API, don't hesitate to open a ticket or send a patch to the [issue tracker](https://github.com/rails/rails/issues).
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 5bb895cb78..984480c70f 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -56,11 +56,11 @@ the comment operator on that line to later enable the asset pipeline:
To set asset compression methods, set the appropriate configuration options
in `production.rb` - `config.assets.css_compressor` for your CSS and
-`config.assets.js_compressor` for your Javascript:
+`config.assets.js_compressor` for your JavaScript:
```ruby
config.assets.css_compressor = :yui
-config.assets.js_compressor = :uglify
+config.assets.js_compressor = :uglifier
```
NOTE: The `sass-rails` gem is automatically used for CSS compression if included
@@ -198,12 +198,9 @@ will result in your assets being included more than once.
WARNING: When using asset precompilation, you will need to ensure that your
controller assets will be precompiled when loading them on a per page basis. By
-default .coffee and .scss files will not be precompiled on their own. This will
-result in false positives during development as these files will work just fine
-since assets are compiled on the fly in development mode. When running in
-production, however, you will see 500 errors since live compilation is turned
-off by default. See [Precompiling Assets](#precompiling-assets) for more
-information on how precompiling works.
+default .coffee and .scss files will not be precompiled on their own. See
+[Precompiling Assets](#precompiling-assets) for more information on how
+precompiling works.
NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript.
If you are using Mac OS X or Windows, you have a JavaScript runtime installed in
@@ -245,7 +242,7 @@ When a file is referenced from a manifest or a helper, Sprockets searches the
three default asset locations for it.
The default locations are: the `images`, `javascripts` and `stylesheets`
-directories under the `apps/assets` folder, but these subdirectories
+directories under the `app/assets` folder, but these subdirectories
are not special - any path under `assets/*` will be searched.
For example, these files:
@@ -581,23 +578,21 @@ runtime. To disable this behavior you can set:
config.assets.raise_runtime_errors = false
```
-When `raise_runtime_errors` is set to `false` sprockets will not check that dependencies of assets are declared properly. Here is a scenario where you must tell the asset pipeline about a dependency:
-
-If you have `application.css.erb` that references `logo.png` like this:
+When this option is true, the asset pipeline will check if all the assets loaded
+in your application are included in the `config.assets.precompile` list.
+If `config.assets.digests` is also true, the asset pipeline will require that
+all requests for assets include digests.
-```css
-#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
-```
+### Turning Digests Off
-Then you must declare that `logo.png` is a dependency of `application.css.erb`, so when the image gets re-compiled, the css file does as well. You can do this using the `//= depend_on_asset` declaration:
+You can turn off digests by updating `config/environments/development.rb` to
+include:
-```css
-//= depend_on_asset "logo.png"
-#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
+```ruby
+config.assets.digests = false
```
-Without this declaration you may experience strange behavior when pushing to production that is difficult to debug. When you have `raise_runtime_errors` set to `true`, dependencies will be checked at runtime so you can ensure that all dependencies are met.
-
+When this option is true, digests will be generated for asset URLs.
### Turning Debugging Off
@@ -691,7 +686,7 @@ information on compiling locally.
The rake task is:
```bash
-$ RAILS_ENV=production bundle exec rake assets:precompile
+$ RAILS_ENV=production bin/rake assets:precompile
```
Capistrano (v2.15.1 and above) includes a recipe to handle this in deployment.
@@ -724,17 +719,17 @@ JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and
`.scss` files are **not** automatically included as they compile to JS/CSS.
If you have other manifests or individual stylesheets and JavaScript files to
-include, you can add them to the `precompile` array in `config/application.rb`:
+include, you can add them to the `precompile` array in `config/initializers/assets.rb`:
```ruby
-config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
+Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
```
Or, you can opt to precompile all assets with something like this:
```ruby
-# config/application.rb
-config.assets.precompile << Proc.new do |path|
+# config/initializers/assets.rb
+Rails.application.config.assets.precompile << Proc.new do |path|
if path =~ /\.(css|js)\z/
full_path = Rails.application.assets.resolve(path).to_path
app_assets_path = Rails.root.join('app', 'assets').to_path
@@ -781,7 +776,7 @@ exception indicating the name of the missing file(s).
#### Far-future Expires Header
-Precompiled assets exist on the filesystem and are served directly by your web
+Precompiled assets exist on the file system and are served directly by your web
server. 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 those
headers.
@@ -928,6 +923,14 @@ that it plays nicely with the pipeline. You may find quirks related to your
specific set up, you may not. The defaults nginx uses, for example, should give
you no problems when used as an HTTP cache.
+If you want to serve only some assets from your CDN, you can use custom
+`:host` option of `asset_url` helper, which overwrites value set in
+`config.action_controller.asset_host`.
+
+```ruby
+asset_url 'image.png', :host => 'http://cdn.example.com'
+```
+
Customizing the Pipeline
------------------------
@@ -943,7 +946,7 @@ gem.
```ruby
config.assets.css_compressor = :yui
```
-The other option for compressing CSS if you have the sass-rails gem installed is
+The other option for compressing CSS if you have the sass-rails gem installed is
```ruby
config.assets.css_compressor = :sass
@@ -1018,7 +1021,7 @@ The X-Sendfile header is a directive to the web server to ignore the response
from the application, and instead serve a specified file from disk. 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. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file)
+faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file)
on how to use this feature.
Apache and nginx support this option, which can be enabled in
@@ -1056,6 +1059,14 @@ cache store.
config.assets.cache_store = :memory_store, { size: 32.megabytes }
```
+To disable the assets cache store:
+
+```ruby
+config.assets.configure do |env|
+ env.cache = ActiveSupport::Cache.lookup_store(:null_store)
+end
+```
+
Adding Assets to Your Gems
--------------------------
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 5ec6ae0f21..df38bd7321 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -571,7 +571,7 @@ If you create an association some time after you build the underlying model, you
If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.
-WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper\_boxes" and "papers" to generate a join table name of "papers\_paper\_boxes" because of the length of the name "paper\_boxes", but it in fact generates a join table name of "paper\_boxes\_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings).
+WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '_' is lexicographically _less_ than 's' in common encodings).
Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations:
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index e898d75d1a..c652aa6a80 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -30,13 +30,13 @@ config.action_controller.perform_caching = true
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.
-INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
+INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
### Action Caching
Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy.
-INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
+INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method.
### Fragment Caching
@@ -105,7 +105,7 @@ This method generates a cache key that depends on all products and can be used i
<% end %>
```
-If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless`
+If you want to cache a fragment under certain condition you can use `cache_if` or `cache_unless`
```erb
<% cache_if (condition, cache_key_for_products) do %>
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 57283f7c40..0bcdc4011a 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -60,7 +60,7 @@ With no further work, `rails server` will run our new shiny Rails app:
```bash
$ cd commandsapp
-$ rails server
+$ bin/rails server
=> Booting WEBrick
=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
@@ -77,7 +77,7 @@ INFO: You can also use the alias "s" to start the server: `rails s`.
The server can be run on a different port using the `-p` option. The default development environment can be changed using `-e`.
```bash
-$ rails server -e production -p 4000
+$ bin/rails server -e production -p 4000
```
The `-b` option binds Rails to the specified IP, by default it is 0.0.0.0. You can run a server as a daemon by passing a `-d` option.
@@ -89,7 +89,7 @@ The `rails generate` command uses templates to create a whole lot of things. Run
INFO: You can also use the alias "g" to invoke the generator command: `rails g`.
```bash
-$ rails generate
+$ bin/rails generate
Usage: rails generate GENERATOR [args] [options]
...
@@ -114,7 +114,7 @@ Let's make our own controller with the controller generator. But what command sh
INFO: All Rails console utilities have help text. As with most *nix utilities, you can try adding `--help` or `-h` to the end, for example `rails server --help`.
```bash
-$ rails generate controller
+$ bin/rails generate controller
Usage: rails generate controller NAME [action action] [options]
...
@@ -141,7 +141,7 @@ Example:
The controller generator is expecting parameters in the form of `generate controller ControllerName action1 action2`. Let's make a `Greetings` controller with an action of **hello**, which will say something nice to us.
```bash
-$ rails generate controller Greetings hello
+$ bin/rails generate controller Greetings hello
create app/controllers/greetings_controller.rb
route get "greetings/hello"
invoke erb
@@ -182,7 +182,7 @@ Then the view, to display our message (in `app/views/greetings/hello.html.erb`):
Fire up your server using `rails server`.
```bash
-$ rails server
+$ bin/rails server
=> Booting WEBrick...
```
@@ -193,7 +193,7 @@ INFO: With a normal, plain-old Rails application, your URLs will generally follo
Rails comes with a generator for data models too.
```bash
-$ rails generate model
+$ bin/rails generate model
Usage:
rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
@@ -216,7 +216,7 @@ But instead of generating a model directly (which we'll be doing later), let's s
We will set up a simple resource called "HighScore" that will keep track of our highest score on video games we play.
```bash
-$ rails generate scaffold HighScore game:string score:integer
+$ bin/rails generate scaffold HighScore game:string score:integer
invoke active_record
create db/migrate/20130717151933_create_high_scores.rb
create app/models/high_score.rb
@@ -254,10 +254,10 @@ $ rails generate scaffold HighScore game:string score:integer
The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything.
-The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
+The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
```bash
-$ rake db:migrate
+$ bin/rake db:migrate
== CreateHighScores: migrating ===============================================
-- create_table(:high_scores)
-> 0.0017s
@@ -269,7 +269,7 @@ INFO: Let's talk about unit tests. Unit tests are code that tests and makes asse
Let's see the interface Rails created for us.
```bash
-$ rails server
+$ bin/rails server
```
Go to your browser and open [http://localhost:3000/high_scores](http://localhost:3000/high_scores), now we can create new high scores (55,160 on Space Invaders!)
@@ -283,13 +283,13 @@ INFO: You can also use the alias "c" to invoke the console: `rails c`.
You can specify the environment in which the `console` command should operate.
```bash
-$ rails console staging
+$ bin/rails console staging
```
If you wish to test out some code without changing any data, you can do that by invoking `rails console --sandbox`.
```bash
-$ rails console --sandbox
+$ bin/rails console --sandbox
Loading development environment in sandbox (Rails 4.0.0)
Any modifications you make will be rolled back on exit
irb(main):001:0>
@@ -306,7 +306,7 @@ INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`.
`runner` runs Ruby code in the context of Rails non-interactively. For instance:
```bash
-$ rails runner "Model.long_running_method"
+$ bin/rails runner "Model.long_running_method"
```
INFO: You can also use the alias "r" to invoke the runner: `rails r`.
@@ -314,7 +314,7 @@ INFO: You can also use the alias "r" to invoke the runner: `rails r`.
You can specify the environment in which the `runner` command should operate using the `-e` switch.
```bash
-$ rails runner -e staging "Model.long_running_method"
+$ bin/rails runner -e staging "Model.long_running_method"
```
### `rails destroy`
@@ -324,7 +324,7 @@ Think of `destroy` as the opposite of `generate`. It'll figure out what generate
INFO: You can also use the alias "d" to invoke the destroy command: `rails d`.
```bash
-$ rails generate model Oops
+$ bin/rails generate model Oops
invoke active_record
create db/migrate/20120528062523_create_oops.rb
create app/models/oops.rb
@@ -333,7 +333,7 @@ $ rails generate model Oops
create test/fixtures/oops.yml
```
```bash
-$ rails destroy model Oops
+$ bin/rails destroy model Oops
invoke active_record
remove db/migrate/20120528062523_create_oops.rb
remove app/models/oops.rb
@@ -353,9 +353,10 @@ To get the full backtrace for running rake task you can pass the option
```--trace``` to command line, for example ```rake db:create --trace```.
```bash
-$ rake --tasks
+$ bin/rake --tasks
rake about # List versions of all Rails frameworks and the environment
-rake assets:clean # Remove compiled assets
+rake assets:clean # Remove old compiled assets
+rake assets:clobber # Remove compiled assets
rake assets:precompile # Compile all the assets named in config.assets.precompile
rake db:create # Create the database from config/database.yml for the current Rails.env
...
@@ -372,7 +373,7 @@ INFO: You can also use ```rake -T``` to get the list of tasks.
`rake about` gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation.
```bash
-$ rake about
+$ bin/rake about
About your application's environment
Ruby version 1.9.3 (x86_64-linux)
RubyGems version 1.3.6
@@ -393,7 +394,12 @@ Database schema version 20110805173523
### `assets`
-You can precompile the assets in `app/assets` using `rake assets:precompile` and remove those compiled assets using `rake assets:clean`.
+You can precompile the assets in `app/assets` using `rake assets:precompile`,
+and remove older compiled assets using `rake assets:clean`. The `assets:clean`
+task allows for rolling deploys that may still be linking to an old asset while
+the new assets are being built.
+
+If you want to clear `public/assets` completely, you can use `rake assets:clobber`.
### `db`
@@ -414,7 +420,7 @@ The `doc:` namespace has the tools to generate documentation for your app, API d
`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations.
```bash
-$ rake notes
+$ bin/rake notes
(in /home/foobar/commandsapp)
app/controllers/admin/users_controller.rb:
* [ 20] [TODO] any other way to do this?
@@ -434,7 +440,7 @@ config.annotations.register_extensions("scss", "sass", "less") { |annotation| /\
If you are looking for a specific annotation, say FIXME, you can use `rake notes:fixme`. Note that you have to lower case the annotation's name.
```bash
-$ rake notes:fixme
+$ bin/rake notes:fixme
(in /home/foobar/commandsapp)
app/controllers/admin/users_controller.rb:
* [132] high priority for next deploy
@@ -446,7 +452,7 @@ app/models/school.rb:
You can also use custom annotations in your code and list them using `rake notes:custom` by specifying the annotation using an environment variable `ANNOTATION`.
```bash
-$ rake notes:custom ANNOTATION=BUG
+$ bin/rake notes:custom ANNOTATION=BUG
(in /home/foobar/commandsapp)
app/models/post.rb:
* [ 23] Have to fix this one before pushing!
@@ -458,7 +464,7 @@ By default, `rake notes` will look in the `app`, `config`, `lib`, `bin` and `tes
```bash
$ export SOURCE_ANNOTATION_DIRECTORIES='spec,vendor'
-$ rake notes
+$ bin/rake notes
(in /home/foobar/commandsapp)
app/models/user.rb:
* [ 35] [FIXME] User should have a subscription at this point
@@ -530,9 +536,9 @@ end
Invocation of the tasks will look like:
```bash
-rake task_name
-rake "task_name[value 1]" # entire argument string should be quoted
-rake db:nothing
+$ bin/rake task_name
+$ bin/rake "task_name[value 1]" # entire argument string should be quoted
+$ bin/rake db:nothing
```
NOTE: If your need to interact with your application models, perform database queries and so on, your task should depend on the `environment` task, which will load your application code.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index f8f9e9cbd9..e2283df184 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -56,7 +56,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
end
```
-* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`.
+* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints built-in in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`.
* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array.
@@ -552,7 +552,7 @@ development:
$ echo $DATABASE_URL
postgresql://localhost/my_database
-$ rails runner 'puts ActiveRecord::Base.connections'
+$ bin/rails runner 'puts ActiveRecord::Base.connections'
{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database"}}
```
@@ -569,7 +569,7 @@ development:
$ echo $DATABASE_URL
postgresql://localhost/my_database
-$ rails runner 'puts ActiveRecord::Base.connections'
+$ bin/rails runner 'puts ActiveRecord::Base.connections'
{"development"=>{"adapter"=>"postgresql", "host"=>"localhost", "database"=>"my_database", "pool"=>5}}
```
@@ -585,7 +585,7 @@ development:
$ echo $DATABASE_URL
postgresql://localhost/my_database
-$ rails runner 'puts ActiveRecord::Base.connections'
+$ bin/rails runner 'puts ActiveRecord::Base.connections'
{"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}}
```
@@ -644,11 +644,9 @@ development:
encoding: unicode
database: blog_development
pool: 5
- username: blog
- password:
```
-Prepared Statements can be disabled thus:
+Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`:
```yaml
production:
@@ -656,6 +654,16 @@ production:
prepared_statements: false
```
+If enabled, Active Record will create up to `1000` prepared statements per database connection by default. To modify this behavior you can set `statement_limit` to a different value:
+
+```
+production:
+ adapter: postgresql
+ statement_limit: 200
+```
+
+The more prepared statements in use: the more memory your database will require. If your PostgreSQL database is hitting memory limits, try lowering `statement_limit` or disabling prepared statements.
+
#### Configuring an SQLite3 Database for JRuby Platform
If you choose to use SQLite3 and are using JRuby, your `config/database.yml` will look a little different. Here's the development section:
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 36e3862c6b..ef4633ac1a 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -24,9 +24,9 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge
### Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
+If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it has already been reported. If you do not find any issue addressing it you may proceed to [open a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.)
-At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
+Your issue report should contain a title and a clear description of the issue at the bare minimum. You should include as much relevant information as possible and should at least post a code sample that demonstrates the issue. It would be even better if you could include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment.
@@ -50,7 +50,7 @@ Please don't put "feature request" items into GitHub Issues. If there's a new
feature that you want to see added to Ruby on Rails, you'll need to write the
code yourself - or convince someone else to partner with you to write the code.
Later in this guide you'll find detailed instructions for proposing a patch to
-Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you
+Ruby on Rails. If you enter a wish list item in GitHub Issues with no code, you
can expect it to be marked "invalid" as soon as it's reviewed.
Sometimes, the line between 'bug' and 'feature' is a hard one to draw.
@@ -69,89 +69,6 @@ won't be accepted." But it's the proper place to discuss new ideas. GitHub
Issues are not a particularly good venue for the sometimes long and involved
discussions new features require.
-Setting Up a Development Environment
-------------------------------------
-
-To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to set up the tests on your own computer.
-
-### The Easy Way
-
-The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box).
-
-### The Hard Way
-
-In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html).
-
-
-Running an Application Against Your Local Branch
-------------------------------------------------
-
-The `--dev` flag of `rails new` generates an application that uses your local
-branch:
-
-```bash
-$ cd rails
-$ bundle exec rails new ~/my-test-app --dev
-```
-
-The application generated in `~/my-test-app` runs against your local branch
-and in particular sees any modifications upon server reboot.
-
-
-Testing Active Record
----------------------
-
-This is how you run the Active Record test suite only for SQLite3:
-
-```bash
-$ cd activerecord
-$ bundle exec rake test_sqlite3
-```
-
-You can now run the tests as you did for `sqlite3`. The tasks are respectively
-
-```bash
-test_mysql
-test_mysql2
-test_postgresql
-```
-
-Finally,
-
-```bash
-$ bundle exec rake test
-```
-
-will now run the four of them in turn.
-
-You can also run any single test separately:
-
-```bash
-$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
-```
-
-You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server.
-
-### Warnings
-
-The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
-
-If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
-
-```bash
-$ RUBYOPT=-W0 bundle exec rake test
-```
-
-### Older Versions of Ruby on Rails
-
-If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch:
-
-```bash
-$ git branch --track 3-0-stable origin/3-0-stable
-$ git checkout 3-0-stable
-```
-
-TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with.
Helping to Resolve Existing Issues
----------------------------------
@@ -227,9 +144,21 @@ WARNING: Docrails has a very strict policy: no code can be touched whatsoever, n
Contributing to the Rails Code
------------------------------
+### Setting Up a Development Environment
+
+To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer.
+
+#### The Easy Way
+
+The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box).
+
+#### The Hard Way
+
+In case you can't use the Rails development box, see [this other guide](development_dependencies_install.html).
+
### Clone the Rails Repository
-The first thing you need to do to be able to contribute code is to clone the repository:
+To be able to contribute code, you need to clone the Rails repository:
```bash
$ git clone git://github.com/rails/rails.git
@@ -244,29 +173,31 @@ $ git checkout -b my_new_branch
It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository.
+### Running an Application Against Your Local Branch
+
+In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch:
+
+```bash
+$ cd rails
+$ bundle exec rails new ~/my-test-app --dev
+```
+
+The application generated in `~/my-test-app` runs against your local branch
+and in particular sees any modifications upon server reboot.
+
### Write Your Code
-Now get busy and add or edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind:
+Now get busy and add/edit code. You're on your branch now, so you can write whatever you want (make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind:
* Get the code right.
* Use Rails idioms and helpers.
* Include tests that fail without your code, and pass with it.
* Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution.
-It is not customary in Rails to run the full test suite before pushing
-changes. The railties test suite in particular takes a long time, and even
-more if the source code is mounted in `/vagrant` as happens in the recommended
-workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
-
-As a compromise, test what your code obviously affects, and if the change is
-not in railties, run the whole test suite of the affected component. If all
-tests are passing, that's enough to propose your contribution. We have
-[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching
-unexpected breakages elsewhere.
TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted.
-### Follow the Coding Conventions
+#### Follow the Coding Conventions
Rails follows a simple set of coding style conventions:
@@ -284,13 +215,100 @@ Rails follows a simple set of coding style conventions:
The above are guidelines - please use your best judgment in using them.
+### Running Tests
+
+It is not customary in Rails to run the full test suite before pushing
+changes. The railties test suite in particular takes a long time, and even
+more if the source code is mounted in `/vagrant` as happens in the recommended
+workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box).
+
+As a compromise, test what your code obviously affects, and if the change is
+not in railties, run the whole test suite of the affected component. If all
+tests are passing, that's enough to propose your contribution. We have
+[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching
+unexpected breakages elsewhere.
+
+#### Entire Rails:
+
+To run all the tests, do:
+
+```bash
+$ cd rails
+$ bundle exec rake test
+```
+
+#### For a Particular Component
+
+You can run tests only for a particular component (e.g. Action Pack). For example,
+to run Action Mailer tests:
+
+```bash
+$ cd actionmailer
+$ bundle exec rake test
+```
+
+#### Running a Single Test
+
+You can run a single test through ruby. For instance:
+
+```bash
+$ cd actionmailer
+$ ruby -w -Itest test/mail_layout_test.rb -n test_explicit_class_layout
+```
+
+The `-n` option allows you to run a single method instead of the whole
+file.
+
+##### Testing Active Record
+
+This is how you run the Active Record test suite only for SQLite3:
+
+```bash
+$ cd activerecord
+$ bundle exec rake test:sqlite3
+```
+
+You can now run the tests as you did for `sqlite3`. The tasks are respectively
+
+```bash
+test:mysql
+test:mysql2
+test:postgresql
+```
+
+Finally,
+
+```bash
+$ bundle exec rake test
+```
+
+will now run the four of them in turn.
+
+You can also run any single test separately:
+
+```bash
+$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
+```
+
+You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server.
+
+### Warnings
+
+The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
+
+If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
+
+```bash
+$ RUBYOPT=-W0 bundle exec rake test
+```
+
### Updating the CHANGELOG
The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version.
You should add an entry to the CHANGELOG of the framework that you modified if you're adding or removing a feature, committing a bug fix or adding deprecation notices. Refactorings and documentation changes generally should not go to the CHANGELOG.
-A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach issue's number. Here is an example CHANGELOG entry:
+A CHANGELOG entry should summarize what was changed and should end with author's name and it should go on top of a CHANGELOG. You can use multiple lines if you need more space and you can attach code examples indented with 4 spaces. If a change is related to a specific issue, you should attach the issue's number. Here is an example CHANGELOG entry:
```
* Summary of a change that briefly describes what was changed. You can use multiple
@@ -314,9 +332,9 @@ Your name can be added directly after the last word if you don't provide any cod
You should not be the only person who looks at the code before you submit it.
If you know someone else who uses Rails, try asking them if they'll check out
your work. If you don't know anyone else using Rails, try hopping into the IRC
-room or posting about your idea to the rails-core mailing list. Doing this in
-private before you push a patch out publicly is the “smoke test” for a patch:
-if you can’t convince one other developer of the beauty of your code, you’re
+room or posting about your idea to the rails-core mailing list. Doing this in
+private before you push a patch out publicly is the "smoke test" for a patch:
+if you can't convince one other developer of the beauty of your code, you’re
unlikely to convince the core team either.
### Commit Your Changes
@@ -467,11 +485,11 @@ the same way that you appreciate feedback on your patches.
### Iterate as Necessary
-It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem.
+It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into the knowledge of the community. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem.
#### Squashing commits
-One of the things that we may ask you to do is "squash your commits," which
+One of the things that we may ask you to do is to "squash your commits", which
will combine all of your commits into a single commit. We prefer pull requests
that are a single commit. This makes it easier to backport changes to stable
branches, squashing makes it easier to revert bad commits, and the git history
@@ -507,7 +525,18 @@ $ git push origin my_pull_request -f
You should be able to refresh the pull request on GitHub and see that it has
been updated.
-### Backporting
+### Older Versions of Ruby on Rails
+
+If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 4-0-stable branch:
+
+```bash
+$ git branch --track 4-0-stable origin/4-0-stable
+$ git checkout 4-0-stable
+```
+
+TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with.
+
+#### Backporting
Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort.
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index 7c6858fa2c..8767fbecce 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -64,7 +64,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi
<% end %>
<%= author('Pratik Naik', 'lifo') do %>
- Pratik Naik is a Ruby on Rails developer at <a href="http://www.37signals.com">37signals</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through =&gt; :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>.
+ Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through =&gt; :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>.
<% end %>
<%= author('Emilio Tagua', 'miloops') do %>
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 0e10d1b697..b067d9efb7 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -210,7 +210,7 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "
```
### Impact of Logs on Performance
-Logging will always have a small impact on performance of your rails app,
+Logging will always have a small impact on performance of your rails app,
particularly when logging to disk.However, there are a few subtleties:
Using the `:debug` level will have a greater performance penalty than `:fatal`,
@@ -224,446 +224,576 @@ Another potential pitfall is that if you have many calls to `Logger` like this
logger.debug "Person attributes hash: #{@person.attributes.inspect}"
```
-In the above example, There will be a performance impact even if the allowed
-output level doesn't include debug. The reason is that Ruby has to evaluate
-these strings, which includes instantiating the somewhat heavy `String` object
+In the above example, There will be a performance impact even if the allowed
+output level doesn't include debug. The reason is that Ruby has to evaluate
+these strings, which includes instantiating the somewhat heavy `String` object
and interpolating the variables, and which takes time.
-Therefore, it's recommended to pass blocks to the logger methods, as these are
-only evaluated if the output level is the same or included in the allowed level
+Therefore, it's recommended to pass blocks to the logger methods, as these are
+only evaluated if the output level is the same or included in the allowed level
(i.e. lazy loading). The same code rewritten would be:
```ruby
logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}
```
-The contents of the block, and therefore the string interpolation, is only
-evaluated if debug is enabled. This performance savings is only really
+The contents of the block, and therefore the string interpolation, is only
+evaluated if debug is enabled. This performance savings is only really
noticeable with large amounts of logging, but it's a good practice to employ.
-Debugging with the `debugger` gem
+Debugging with the `byebug` gem
---------------------------------
-When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion.
+When your code is behaving in unexpected ways, you can try printing to logs or
+the console to diagnose the problem. Unfortunately, there are times when this
+sort of error tracking is not effective in finding the root cause of a problem.
+When you actually need to journey into your running source code, the debugger
+is your best companion.
-The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and use this guide to learn how to move from the code you have written deeper into Rails code.
+The debugger can also help you if you want to learn about the Rails source code
+but don't know where to start. Just debug any request to your application and
+use this guide to learn how to move from the code you have written deeper into
+Rails code.
### Setup
-You can use the `debugger` gem to set breakpoints and step through live code in Rails. To install it, just run:
+You can use the `byebug` gem to set breakpoints and step through live code in
+Rails. To install it, just run:
```bash
-$ gem install debugger
+$ gem install byebug
```
-Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the `debugger` method.
+Inside any Rails application you can then invoke the debugger by calling the
+`byebug` method.
Here's an example:
```ruby
class PeopleController < ApplicationController
def new
- debugger
+ byebug
@person = Person.new
end
end
```
-If you see this message in the console or logs:
+### The Shell
+
+As soon as your application calls the `byebug` method, the debugger will be
+started in a debugger shell inside the terminal window where you launched your
+application server, and you will be placed at the debugger's prompt `(byebug)`.
+Before the prompt, the code around the line that is about to be run will be
+displayed and the current line will be marked by '=>'. Like this:
```
-***** Debugger requested, but was not available: Start server with --debugger to enable *****
+[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
+
+(byebug)
```
-Make sure you have started your web server with the option `--debugger`:
+If you got there by a browser request, the browser tab containing the request
+will be hung until the debugger has finished and the trace has finished
+processing the entire request.
+
+For example:
```bash
-$ rails server --debugger
=> Booting WEBrick
-=> Rails 4.0.0 application starting on http://0.0.0.0:3000
-=> Debugger enabled
-...
-```
+=> Rails 4.1.0 application starting in development on http://0.0.0.0:3000
+=> Run `rails server -h` for more startup options
+=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
+=> Ctrl-C to shutdown server
+[2014-04-11 13:11:47] INFO WEBrick 1.3.1
+[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux]
+[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000
-TIP: In development mode, you can dynamically `require \'debugger\'` instead of restarting the server, even if it was started without `--debugger`.
-### The Shell
+Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200
+ ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations"
+Processing by PostsController#index as HTML
-As soon as your application calls the `debugger` method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt `(rdb:n)`. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run.
+[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
-If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request.
+(byebug)
+```
-For example:
+Now it's time to explore and dig into your application. A good place to start is
+by asking the debugger for help. Type: `help`
-```bash
-@posts = Post.all
-(rdb:7)
```
+(byebug) help
-Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help. Type: `help`
+byebug 2.7.0
-```
-(rdb:7) help
-ruby-debug help v0.10.2
Type 'help <command-name>' for help on a specific command
Available commands:
-backtrace delete enable help next quit show trace
-break disable eval info p reload source undisplay
-catch display exit irb pp restart step up
-condition down finish list ps save thread var
-continue edit frame method putl set tmate where
+backtrace delete enable help list pry next restart source up
+break disable eval info method ps save step var
+catch display exit interrupt next putl set thread
+condition down finish irb p quit show trace
+continue edit frame kill pp reload skip undisplay
```
-TIP: To view the help menu for any command use `help <command-name>` at the debugger prompt. For example: _`help var`_
-
-The next command to learn is one of the most useful: `list`. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use `l` for the `list` command.
+TIP: To view the help menu for any command use `help <command-name>` at the
+debugger prompt. For example: _`help list`_. You can abbreviate any debugging
+command by supplying just enough letters to distinguish them from other
+commands, so you can also use `l` for the `list` command, for example.
-This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by `=>`.
+To see the previous ten lines you should type `list-` (or `l-`)
```
-(rdb:7) list
+(byebug) l-
+
[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
1 class PostsController < ApplicationController
- 2 # GET /posts
- 3 # GET /posts.json
- 4 def index
- 5 debugger
-=> 6 @posts = Post.all
- 7
- 8 respond_to do |format|
- 9 format.html # index.html.erb
- 10 format.json { render json: @posts }
-```
+ 2 before_action :set_post, only: [:show, :edit, :update, :destroy]
+ 3
+ 4 # GET /posts
+ 5 # GET /posts.json
+ 6 def index
+ 7 byebug
+ 8 @posts = Post.find_recent
+ 9
+ 10 respond_to do |format|
-If you repeat the `list` command, this time using just `l`, the next ten lines of the file will be printed out.
-
-```
-(rdb:7) l
-[11, 20] in /PathTo/project/app/controllers/posts_controller.rb
- 11 end
- 12 end
- 13
- 14 # GET /posts/1
- 15 # GET /posts/1.json
- 16 def show
- 17 @post = Post.find(params[:id])
- 18
- 19 respond_to do |format|
- 20 format.html # show.html.erb
```
-And so on until the end of the current file. When the end of file is reached, the `list` command will start again from the beginning of the file and continue again up to the end, treating the file as a circular buffer.
+This way you can move inside the file, being able to see the code above and over
+the line where you added the `byebug` call. Finally, to see where you are in
+the code again you can type `list=`
-On the other hand, to see the previous ten lines you should type `list-` (or `l-`)
-
-```
-(rdb:7) l-
-[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
- 1 class PostsController < ApplicationController
- 2 # GET /posts
- 3 # GET /posts.json
- 4 def index
- 5 debugger
- 6 @posts = Post.all
- 7
- 8 respond_to do |format|
- 9 format.html # index.html.erb
- 10 format.json { render json: @posts }
```
+(byebug) list=
-This way you can move inside the file, being able to see the code above and over the line you added the `debugger`.
-Finally, to see where you are in the code again you can type `list=`
+[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
-```
-(rdb:7) list=
-[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
- 1 class PostsController < ApplicationController
- 2 # GET /posts
- 3 # GET /posts.json
- 4 def index
- 5 debugger
-=> 6 @posts = Post.all
- 7
- 8 respond_to do |format|
- 9 format.html # index.html.erb
- 10 format.json { render json: @posts }
+(byebug)
```
### The Context
-When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack.
-
-The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped.
-
-At any time you can call the `backtrace` command (or its alias `where`) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then `backtrace` will supply the answer.
-
-```
-(rdb:5) where
- #0 PostsController.index
- at line /PathTo/project/app/controllers/posts_controller.rb:6
- #1 Kernel.send
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
- #2 ActionController::Base.perform_action_without_filters
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
- #3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...)
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617
+When you start debugging your application, you will be placed in different
+contexts as you go through the different parts of the stack.
+
+The debugger creates a context when a stopping point or an event is reached. The
+context has information about the suspended program which enables the debugger
+to inspect the frame stack, evaluate variables from the perspective of the
+debugged program, and contains information about the place where the debugged
+program is stopped.
+
+At any time you can call the `backtrace` command (or its alias `where`) to print
+the backtrace of the application. This can be very helpful to know how you got
+where you are. If you ever wondered about how you got somewhere in your code,
+then `backtrace` will supply the answer.
+
+```
+(byebug) where
+--> #0 PostsController.index
+ at /PathTo/project/test_app/app/controllers/posts_controller.rb:8
+ #1 ActionController::ImplicitRender.send_action(method#String, *args#Array)
+ at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/implicit_render.rb:4
+ #2 AbstractController::Base.process_action(action#NilClass, *args#Array)
+ at /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb:189
+ #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass)
+ at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/rendering.rb:10
...
```
-You move anywhere you want in this trace (thus changing the context) by using the `frame _n_` command, where _n_ is the specified frame number.
+The current frame is marked with `-->`. You can move anywhere you want in this
+trace (thus changing the context) by using the `frame _n_` command, where _n_ is
+the specified frame number. If you do that, `byebug` will display your new
+context.
```
-(rdb:5) frame 2
-#2 ActionController::Base.perform_action_without_filters
- at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175
+(byebug) frame 2
+
+[184, 193] in /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb
+ 184: # is the intended way to override action dispatching.
+ 185: #
+ 186: # Notice that the first argument is the method to be dispatched
+ 187: # which is *not* necessarily the same as the action name.
+ 188: def process_action(method_name, *args)
+=> 189: send_action(method_name, *args)
+ 190: end
+ 191:
+ 192: # Actually call the method associated with the action. Override
+ 193: # this method if you wish to change how action methods are called,
+
+(byebug)
```
-The available variables are the same as if you were running the code line by line. After all, that's what debugging is.
+The available variables are the same as if you were running the code line by
+line. After all, that's what debugging is.
-Moving up and down the stack frame: You can use `up [n]` (`u` for abbreviated) and `down [n]` commands in order to change the context _n_ frames up or down the stack respectively. _n_ defaults to one. Up in this case is towards higher-numbered stack frames, and down is towards lower-numbered stack frames.
+You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order
+to change the context _n_ frames up or down the stack respectively. _n_ defaults
+to one. Up in this case is towards higher-numbered stack frames, and down is
+towards lower-numbered stack frames.
### Threads
-The debugger can list, stop, resume and switch between running threads by using the command `thread` (or the abbreviated `th`). This command has a handful of options:
+The debugger can list, stop, resume and switch between running threads by using
+the `thread` command (or the abbreviated `th`). This command has a handful of
+options:
* `thread` shows the current thread.
-* `thread list` is used to list all threads and their statuses. The plus + character and the number indicates the current thread of execution.
+* `thread list` is used to list all threads and their statuses. The plus +
+character and the number indicates the current thread of execution.
* `thread stop _n_` stop thread _n_.
* `thread resume _n_` resumes thread _n_.
* `thread switch _n_` switches the current thread context to _n_.
-This command is very helpful, among other occasions, when you are debugging concurrent threads and need to verify that there are no race conditions in your code.
+This command is very helpful, among other occasions, when you are debugging
+concurrent threads and need to verify that there are no race conditions in your
+code.
### Inspecting Variables
-Any expression can be evaluated in the current context. To evaluate an expression, just type it!
-
-This example shows how you can print the instance_variables defined within the current context:
-
-```
-@posts = Post.all
-(rdb:11) instance_variables
-["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"]
-```
-
-As you may have figured out, all of the variables that you can access from a controller are displayed. This list is dynamically updated as you execute code. For example, run the next line using `next` (you'll learn more about this command later in this guide).
-
-```
-(rdb:11) next
-Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET]
- Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e
- Parameters: {"action"=>"index", "controller"=>"posts"}
-/PathToProject/posts_controller.rb:8
-respond_to do |format|
+Any expression can be evaluated in the current context. To evaluate an
+expression, just type it!
+
+This example shows how you can print the instance variables defined within the
+current context:
+
+```
+[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+ 3:
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: byebug
+=> 8: @posts = Post.find_recent
+ 9:
+ 10: respond_to do |format|
+ 11: format.html # index.html.erb
+ 12: format.json { render json: @posts }
+
+(byebug) instance_variables
+[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
+ :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name,
+ :@_response_body, :@marked_for_same_origin_verification, :@_config]
+```
+
+As you may have figured out, all of the variables that you can access from a
+controller are displayed. This list is dynamically updated as you execute code.
+For example, run the next line using `next` (you'll learn more about this
+command later in this guide).
+
+```
+(byebug) next
+[5, 14] in /PathTo/project/app/controllers/posts_controller.rb
+ 5 # GET /posts.json
+ 6 def index
+ 7 byebug
+ 8 @posts = Post.find_recent
+ 9
+=> 10 respond_to do |format|
+ 11 format.html # index.html.erb
+ 12 format.json { render json: @posts }
+ 13 end
+ 14 end
+ 15
+(byebug)
```
And then ask again for the instance_variables:
```
-(rdb:11) instance_variables.include? "@posts"
+(byebug) instance_variables.include? "@posts"
true
```
-Now `@posts` is included in the instance variables, because the line defining it was executed.
+Now `@posts` is included in the instance variables, because the line defining it
+was executed.
-TIP: You can also step into **irb** mode with the command `irb` (of course!). This way an irb session will be started within the context you invoked it. But be warned: this is an experimental feature.
+TIP: You can also step into **irb** mode with the command `irb` (of course!).
+This way an irb session will be started within the context you invoked it. But
+be warned: this is an experimental feature.
-The `var` method is the most convenient way to show variables and their values:
+The `var` method is the most convenient way to show variables and their values.
+Let's let `byebug` to help us with it.
```
-var
-(rdb:1) v[ar] const <object> show constants of object
-(rdb:1) v[ar] g[lobal] show global variables
-(rdb:1) v[ar] i[nstance] <object> show instance variables of object
-(rdb:1) v[ar] l[ocal] show local variables
+(byebug) help var
+v[ar] cl[ass] show class variables of self
+v[ar] const <object> show constants of object
+v[ar] g[lobal] show global variables
+v[ar] i[nstance] <object> show instance variables of object
+v[ar] l[ocal] show local variables
```
-This is a great way to inspect the values of the current context variables. For example:
+This is a great way to inspect the values of the current context variables. For
+example, to check that we have no local variables currently defined.
```
-(rdb:9) var local
- __dbg_verbose_save => false
+(byebug) var local
+(byebug)
```
You can also inspect for an object method this way:
```
-(rdb:9) var instance Post.new
-@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"...
+(byebug) var instance Post.new
+@_start_transaction_state = {}
+@aggregation_cache = {}
+@association_cache = {}
+@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil}
@attributes_cache = {}
-@new_record = true
+@changed_attributes = nil
+...
```
-TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate Ruby expressions and display the value of variables to the console.
+TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate
+Ruby expressions and display the value of variables to the console.
-You can use also `display` to start watching variables. This is a good way of tracking the values of a variable while the execution goes on.
+You can use also `display` to start watching variables. This is a good way of
+tracking the values of a variable while the execution goes on.
```
-(rdb:1) display @recent_comments
-1: @recent_comments =
+(byebug) display @posts
+1: @posts = nil
```
-The variables inside the displaying list will be printed with their values after you move in the stack. To stop displaying a variable use `undisplay _n_` where _n_ is the variable number (1 in the last example).
+The variables inside the displaying list will be printed with their values after
+you move in the stack. To stop displaying a variable use `undisplay _n_` where
+_n_ is the variable number (1 in the last example).
### Step by Step
-Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution.
+Now you should know where you are in the running trace and be able to print the
+available variables. But lets continue and move on with the application
+execution.
-Use `step` (abbreviated `s`) to continue running your program until the next logical stopping point and return control to the debugger.
+Use `step` (abbreviated `s`) to continue running your program until the next
+logical stopping point and return control to the debugger.
-TIP: You can also use `step+ n` and `step- n` to move forward or backward `n` steps respectively.
+You may also use `next` which is similar to step, but function or method calls
+that appear within the line of code are executed without stopping.
-You may also use `next` which is similar to step, but function or method calls that appear within the line of code are executed without stopping. As with step, you may use plus sign to move _n_ steps.
+TIP: You can also use `step n` or `next n` to move forwards `n` steps at once.
-The difference between `next` and `step` is that `step` stops at the next line of code executed, doing just a single step, while `next` moves to the next line without descending inside methods.
+The difference between `next` and `step` is that `step` stops at the next line
+of code executed, doing just a single step, while `next` moves to the next line
+without descending inside methods.
-For example, consider this block of code with an included `debugger` statement:
+For example, consider the following situation:
```ruby
-class Author < ActiveRecord::Base
- has_one :editorial
- has_many :comments
+Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200
+Processing by PostsController#index as HTML
- def find_recent_comments(limit = 10)
- debugger
- @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
- end
-end
+[1, 8] in /home/davidr/Proyectos/test_app/app/models/post.rb
+ 1: class Post < ActiveRecord::Base
+ 2:
+ 3: def self.find_recent(limit = 10)
+ 4: byebug
+=> 5: where('created_at > ?', 1.week.ago).limit(limit)
+ 6: end
+ 7:
+ 8: end
+
+(byebug)
```
-TIP: You can use the debugger while using `rails console`. Just remember to `require "debugger"` before calling the `debugger` method.
+If we use `next`, we want go deep inside method calls. Instead, byebug will go
+to the next line within the same context. In this case, this is the last line of
+the method, so `byebug` will jump to next next line of the previous frame.
```
-$ rails console
-Loading development environment (Rails 4.0.0)
->> require "debugger"
-=> []
->> author = Author.first
-=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10">
->> author.find_recent_comments
-/PathTo/project/app/models/author.rb:11
-)
-```
+(byebug) next
+Next went up a frame because previous frame finished
-With the code stopped, take a look around:
+[4, 13] in /PathTo/project/test_app/app/controllers/posts_controller.rb
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: @posts = Post.find_recent
+ 8:
+=> 9: respond_to do |format|
+ 10: format.html # index.html.erb
+ 11: format.json { render json: @posts }
+ 12: end
+ 13: end
-```
-(rdb:1) list
-[2, 9] in /PathTo/project/app/models/author.rb
- 2 has_one :editorial
- 3 has_many :comments
- 4
- 5 def find_recent_comments(limit = 10)
- 6 debugger
-=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit)
- 8 end
- 9 end
+(byebug)
```
-You are at the end of the line, but... was this line executed? You can inspect the instance variables.
+If we use `step` in the same situation, we will literally go the next ruby
+instruction to be executed. In this case, the activesupport's `week` method.
```
-(rdb:1) var instance
-@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
-@attributes_cache = {}
-```
+(byebug) step
-`@recent_comments` hasn't been defined yet, so it's clear that this line hasn't been executed yet. Use the `next` command to move on in the code:
+[50, 59] in /PathToGems/activesupport-4.1.0/lib/active_support/core_ext/numeric/time.rb
+ 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
+ 51: end
+ 52: alias :day :days
+ 53:
+ 54: def weeks
+=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
+ 56: end
+ 57: alias :week :weeks
+ 58:
+ 59: def fortnights
+(byebug)
```
-(rdb:1) next
-/PathTo/project/app/models/author.rb:12
-@recent_comments
-(rdb:1) var instance
-@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las...
-@attributes_cache = {}
-@comments = []
-@recent_comments = []
-```
-
-Now you can see that the `@comments` relationship was loaded and @recent_comments defined because the line was executed.
-If you want to go deeper into the stack trace you can move single `steps`, through your calling methods and into Rails code. This is one of the best ways to find bugs in your code, or perhaps in Ruby or Rails.
+This is one of the best ways to find bugs in your code, or perhaps in Ruby on
+Rails.
### Breakpoints
-A breakpoint makes your application stop whenever a certain point in the program is reached. The debugger shell is invoked in that line.
+A breakpoint makes your application stop whenever a certain point in the program
+is reached. The debugger shell is invoked in that line.
-You can add breakpoints dynamically with the command `break` (or just `b`). There are 3 possible ways of adding breakpoints manually:
+You can add breakpoints dynamically with the command `break` (or just `b`).
+There are 3 possible ways of adding breakpoints manually:
* `break line`: set breakpoint in the _line_ in the current source file.
-* `break file:line [if expression]`: set breakpoint in the _line_ number inside the _file_. If an _expression_ is given it must evaluated to _true_ to fire up the debugger.
-* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The _expression_ works the same way as with file:line.
+* `break file:line [if expression]`: set breakpoint in the _line_ number inside
+the _file_. If an _expression_ is given it must evaluated to _true_ to fire up
+the debugger.
+* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and
+\# for class and instance method respectively) defined in _class_. The
+_expression_ works the same way as with file:line.
+
+
+For example, in the previous situation
```
-(rdb:5) break 10
-Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10
+[4, 13] in /PathTo/project/app/controllers/posts_controller.rb
+ 4: # GET /posts
+ 5: # GET /posts.json
+ 6: def index
+ 7: @posts = Post.find_recent
+ 8:
+=> 9: respond_to do |format|
+ 10: format.html # index.html.erb
+ 11: format.json { render json: @posts }
+ 12: end
+ 13: end
+
+(byebug) break 11
+Created breakpoint 1 at /PathTo/project/app/controllers/posts_controller.rb:11
+
```
-Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you supply a number, it lists that breakpoint. Otherwise it lists all breakpoints.
+Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you
+supply a number, it lists that breakpoint. Otherwise it lists all breakpoints.
```
-(rdb:5) info breakpoints
+(byebug) info breakpoints
Num Enb What
- 1 y at filters.rb:10
+1 y at /PathTo/project/app/controllers/posts_controller.rb:11
```
-To delete breakpoints: use the command `delete _n_` to remove the breakpoint number _n_. If no number is specified, it deletes all breakpoints that are currently active..
+To delete breakpoints: use the command `delete _n_` to remove the breakpoint
+number _n_. If no number is specified, it deletes all breakpoints that are
+currently active.
```
-(rdb:5) delete 1
-(rdb:5) info breakpoints
+(byebug) delete 1
+(byebug) info breakpoints
No breakpoints.
```
You can also enable or disable breakpoints:
-* `enable breakpoints`: allow a list _breakpoints_ or all of them if no list is specified, to stop your program. This is the default state when you create a breakpoint.
+* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is
+specified, to stop your program. This is the default state when you create a
+breakpoint.
* `disable breakpoints`: the _breakpoints_ will have no effect on your program.
### Catching Exceptions
-The command `catch exception-name` (or just `cat exception-name`) can be used to intercept an exception of type _exception-name_ when there would otherwise be is no handler for it.
+The command `catch exception-name` (or just `cat exception-name`) can be used to
+intercept an exception of type _exception-name_ when there would otherwise be no
+handler for it.
To list all active catchpoints use `catch`.
### Resuming Execution
-There are two ways to resume execution of an application that is stopped in the debugger:
-
-* `continue` [line-specification] \(or `c`): resume program execution, at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argument line-specification allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached.
-* `finish` [frame-number] \(or `fin`): execute until the selected stack frame returns. If no frame number is given, the application will run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been performed. If a frame number is given it will run until the specified frame returns.
+There are two ways to resume execution of an application that is stopped in the
+debugger:
+
+* `continue` [line-specification] \(or `c`): resume program execution, at the
+address where your script last stopped; any breakpoints set at that address are
+bypassed. The optional argument line-specification allows you to specify a line
+number to set a one-time breakpoint which is deleted when that breakpoint is
+reached.
+* `finish` [frame-number] \(or `fin`): execute until the selected stack frame
+returns. If no frame number is given, the application will run until the
+currently selected frame returns. The currently selected frame starts out the
+most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been
+performed. If a frame number is given it will run until the specified frame
+returns.
### Editing
Two commands allow you to open code from the debugger into an editor:
-* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR environment variable. A specific _line_ can also be given.
-* `tmate _n_` (abbreviated `tm`): open the current file in TextMate. It uses n-th frame if _n_ is specified.
+* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR
+environment variable. A specific _line_ can also be given.
### Quitting
-To exit the debugger, use the `quit` command (abbreviated `q`), or its alias `exit`.
+To exit the debugger, use the `quit` command (abbreviated `q`), or its alias
+`exit`.
-A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again.
+A simple quit tries to terminate all threads in effect. Therefore your server
+will be stopped and you will have to start it again.
### Settings
-The `debugger` gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options:
-
-* `set reload`: Reload source code when changed.
-* `set autolist`: Execute `list` command on every breakpoint.
-* `set listsize _n_`: Set number of source lines to list by default to _n_.
-* `set forcestep`: Make sure the `next` and `step` commands always move to a new line
+`byebug` has a few available options to tweak its behaviour:
-You can see the full list by using `help set`. Use `help set _subcommand_` to learn about a particular `set` command.
+* `set autoreload`: Reload source code when changed (default: true).
+* `set autolist`: Execute `list` command on every breakpoint (default: true).
+* `set listsize _n_`: Set number of source lines to list by default to _n_
+(default: 10)
+* `set forcestep`: Make sure the `next` and `step` commands always move to a new
+line.
-TIP: You can save these settings in an `.rdebugrc` file in your home directory. The debugger reads these global settings when it starts.
+You can see the full list by using `help set`. Use `help set _subcommand_` to
+learn about a particular `set` command.
-Here's a good start for an `.rdebugrc`:
+TIP: You can save these settings in an `.byebugrc` file in your home directory.
+The debugger reads these global settings when it starts. For example:
```bash
-set autolist
set forcestep
set listsize 25
```
@@ -671,35 +801,59 @@ set listsize 25
Debugging Memory Leaks
----------------------
-A Ruby application (on Rails or not), can leak memory - either in the Ruby code or at the C code level.
+A Ruby application (on Rails or not), can leak memory - either in the Ruby code
+or at the C code level.
-In this section, you will learn how to find and fix such leaks by using tool such as Valgrind.
+In this section, you will learn how to find and fix such leaks by using tool
+such as Valgrind.
### Valgrind
-[Valgrind](http://valgrind.org/) is a Linux-only application for detecting C-based memory leaks and race conditions.
+[Valgrind](http://valgrind.org/) is a Linux-only application for detecting
+C-based memory leaks and race conditions.
-There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C extension in the interpreter calls `malloc()` but doesn't properly call `free()`, this memory won't be available until the app terminates.
+There are Valgrind tools that can automatically detect many memory management
+and threading bugs, and profile your programs in detail. For example, if a C
+extension in the interpreter calls `malloc()` but doesn't properly call
+`free()`, this memory won't be available until the app terminates.
-For further information on how to install Valgrind and use with Ruby, refer to [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) by Evan Weaver.
+For further information on how to install Valgrind and use with Ruby, refer to
+[Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/)
+by Evan Weaver.
Plugins for Debugging
---------------------
-There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging:
-
-* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has footnotes that give request information and link back to your source via TextMate.
-* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query origin tracing to your logs.
-* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed.
-* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application.
-* [Better Errors](https://github.com/charliesome/better_errors) Replaces the standard Rails error page with a new one containing more contextual information, like source code and variable inspection.
-* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails development that will end your tailing of development.log. Have all information about your Rails app requests in the browser - in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more.
+There are some Rails plugins to help you to find errors and debug your
+application. Here is a list of useful plugins for debugging:
+
+* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has
+footnotes that give request information and link back to your source via
+TextMate.
+* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query
+origin tracing to your logs.
+* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin
+not only runs "EXPLAIN" before each of your select queries in development, but
+provides a small DIV in the rendered output of each page with the summary of
+warnings for each query that it analyzed.
+* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master)
+Provides a mailer object and a default set of templates for sending email
+notifications when errors occur in a Rails application.
+* [Better Errors](https://github.com/charliesome/better_errors) Replaces the
+standard Rails error page with a new one containing more contextual information,
+like source code and variable inspection.
+* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails
+development that will end your tailing of development.log. Have all information
+about your Rails app requests in the browser - in the Developer Tools panel.
+Provides insight to db/rendering/total times, parameter list, rendered views and
+more.
References
----------
* [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html)
* [debugger Homepage](https://github.com/cldwalker/debugger)
+* [byebug Homepage](https://github.com/deivid-rodriguez/byebug)
* [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/)
* [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised)
* [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace)
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 4ee43b6a97..b134c9d2d0 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -117,7 +117,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby
NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running.
-You can use homebrew to install memcached on OSX:
+You can use [Homebrew](http://brew.sh/) to install memcached on OSX:
```bash
$ brew install memcached
@@ -210,6 +210,14 @@ FreeBSD users will have to run the following:
# pkg_add -r postgresql92-client postgresql92-server
```
+You can use [Homebrew](http://brew.sh/) to install MySQL and PostgreSQL on OSX:
+
+```bash
+$ brew install mysql
+$ brew install postgresql
+```
+Follow instructions given by [Homebrew](http://brew.sh/) to start these.
+
Or install them through ports (they are located under the `databases` folder).
If you run into troubles during the installation of MySQL, please see
[the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html).
@@ -241,20 +249,25 @@ and create the test databases:
```bash
$ cd activerecord
-$ bundle exec rake mysql:build_databases
+$ bundle exec rake db:mysql:build
```
PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account
+This is not needed when installed via [Homebrew](http://brew.sh).
```bash
$ sudo -u postgres createuser --superuser $USER
```
+And for OS X (when installed via [Homebrew](http://brew.sh))
+```bash
+$ createuser --superuser $USER
+```
and then create the test databases with
```bash
$ cd activerecord
-$ bundle exec rake postgresql:build_databases
+$ bundle exec rake db:postgresql:build
```
It is possible to build databases for both PostgreSQL and MySQL with
diff --git a/guides/source/engines.md b/guides/source/engines.md
index bbd63bb892..724c3d9021 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -73,13 +73,13 @@ options as appropriate to the need. For the "blorgh" example, you will need to
create a "mountable" engine, running this command in a terminal:
```bash
-$ rails plugin new blorgh --mountable
+$ bin/rails plugin new blorgh --mountable
```
The full list of options for the plugin generator may be seen by typing:
```bash
-$ rails plugin --help
+$ bin/rails plugin --help
```
The `--full` option tells the generator that you want to create an engine,
@@ -197,7 +197,7 @@ within the `Engine` class definition. Without it, classes generated in an engine
**may** conflict with an application.
What this isolation of the namespace means is that a model generated by a call
-to `rails g model`, such as `rails g model post`, won't be called `Post`, but
+to `bin/rails g model`, such as `bin/rails g model post`, won't be called `Post`, but
instead be namespaced and called `Blorgh::Post`. In addition, the table for the
model is namespaced, becoming `blorgh_posts`, rather than simply `posts`.
Similar to the model namespacing, a controller called `PostsController` becomes
@@ -253,7 +253,7 @@ This means that you will be able to generate new controllers and models for this
engine very easily by running commands like this:
```bash
-rails g model
+$ bin/rails g model
```
Keep in mind, of course, that anything generated with these commands inside of
@@ -293,7 +293,7 @@ The first thing to generate for a blog engine is the `Post` model and related
controller. To quickly generate this, you can use the Rails scaffold generator.
```bash
-$ rails generate scaffold post title:string text:text
+$ bin/rails generate scaffold post title:string text:text
```
This command will output this information:
@@ -451,7 +451,7 @@ From the application root, run the model generator. Tell it to generate a
and `text` text column.
```bash
-$ rails generate model Comment post_id:integer text:text
+$ bin/rails generate model Comment post_id:integer text:text
```
This will output the following:
@@ -471,7 +471,7 @@ called `Blorgh::Comment`. Now run the migration to create our blorgh_comments
table:
```bash
-$ rake db:migrate
+$ bin/rake db:migrate
```
To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and
@@ -545,7 +545,7 @@ The route now exists, but the controller that this route goes to does not. To
create it, run this command from the application root:
```bash
-$ rails g controller comments
+$ bin/rails g controller comments
```
This will generate the following things:
@@ -682,14 +682,14 @@ engine's models can query them correctly. To copy these migrations into the
application use this command:
```bash
-$ rake blorgh:install:migrations
+$ bin/rake blorgh:install:migrations
```
If you have multiple engines that need migrations copied over, use
`railties:install:migrations` instead:
```bash
-$ rake railties:install:migrations
+$ bin/rake railties:install:migrations
```
This command, when run for the first time, will copy over all the migrations
@@ -810,7 +810,7 @@ of associating the records in the `blorgh_posts` table with the records in the
To generate this new column, run this command within the engine:
```bash
-$ rails g migration add_author_id_to_blorgh_posts author_id:integer
+$ bin/rails g migration add_author_id_to_blorgh_posts author_id:integer
```
NOTE: Due to the migration's name and the column specification after it, Rails
@@ -822,7 +822,7 @@ This migration will need to be run on the application. To do that, it must first
be copied using this command:
```bash
-$ rake blorgh:install:migrations
+$ bin/rake blorgh:install:migrations
```
Notice that only _one_ migration was copied over here. This is because the first
@@ -839,7 +839,7 @@ with the same name already exists. Copied migration
Run the migration using:
```bash
-$ rake db:migrate
+$ bin/rake db:migrate
```
Now with all the pieces in place, an action will take place that will associate
@@ -1052,6 +1052,16 @@ This tells the application that you still want to perform a `GET` request to the
`index` action of this controller, but you want to use the engine's route to get
there, rather than the application's one.
+Another way to do this is to assign the `@routes` instance variable to `Engine.routes` in your test setup:
+
+```ruby
+setup do
+ @routes = Engine.routes
+end
+```
+
+This will also ensure url helpers for the engine will work as expected in your tests.
+
Improving engine functionality
------------------------------
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 205e0f6b62..027b6303fc 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,16 +1,16 @@
Form Helpers
============
-Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails does away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
+Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of the need to handle form control naming and its numerous attributes. Rails does away with this complexity by providing view helpers for generating form markup. However, since these helpers have different use cases, developers need to know the differences between the helper methods before putting them to use.
After reading this guide, you will know:
* How to create search forms and similar kind of generic forms not representing any specific model in your application.
-* How to make model-centric forms for creation and editing of specific database records.
+* How to make model-centric forms for creating and editing specific database records.
* How to generate select boxes from multiple types of data.
-* The date and time helpers Rails provides.
+* What date and time helpers Rails provides.
* What makes a file upload form different.
-* Some cases of building forms to external resources.
+* How to post forms to external resources and specify setting an `authenticity_token`.
* How to build complex forms.
--------------------------------------------------------------------------------
@@ -146,7 +146,7 @@ Output:
<label for="age_adult">I'm over 21</label>
```
-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 of them, and `params[:age]` will contain either "child" or "adult".
NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and,
by expanding the clickable region,
@@ -442,7 +442,12 @@ WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is
You can add arbitrary attributes to the options using hashes:
```html+erb
-<%= options_for_select([['Lisbon', 1, {'data-size' => '2.8 million'}], ['Madrid', 2, {'data-size' => '3.2 million'}]], 2) %>
+<%= options_for_select(
+ [
+ ['Lisbon', 1, { 'data-size' => '2.8 million' }],
+ ['Madrid', 2, { 'data-size' => '3.2 million' }]
+ ], 2
+) %>
output:
@@ -819,21 +824,21 @@ As a shortcut you can append [] to the name and omit the `:index` option. This i
produces exactly the same output as the previous example.
-Forms to external resources
+Forms to External Resources
---------------------------
-If you need to post some data to an external resource it is still great to build your form using rails form helpers. But sometimes you need to set an `authenticity_token` for this resource. You can do it by passing an `authenticity_token: 'your_external_token'` parameter to the `form_tag` options:
+Rails' form helpers can also be used to build a form for posting data to an external resource. However, at times it can be necessary to set an `authenticity_token` for the resource; this can be done by passing an `authenticity_token: 'your_external_token'` parameter to the `form_tag` options:
```erb
-<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token') do %>
+<%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token' do %>
Form contents
<% end %>
```
-Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an `authenticity_token` hidden field at all. For doing this just pass `false` to the `:authenticity_token` option:
+Sometimes when submitting data to an external resource, like a payment gateway, the fields that can be used in the form are limited by an external API and it may be undesirable to generate an `authenticity_token`. To not send a token, simply pass `false` to the `:authenticity_token` option:
```erb
-<%= form_tag 'http://farfar.away/form', authenticity_token: false) do %>
+<%= form_tag 'http://farfar.away/form', authenticity_token: false do %>
Form contents
<% end %>
```
@@ -1008,4 +1013,4 @@ As a convenience you can instead pass the symbol `:all_blank` which will create
### Adding Fields on the Fly
-Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice.
+Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any built-in support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice.
diff --git a/guides/source/generators.md b/guides/source/generators.md
index 4a5377c206..bb9653e3f2 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -23,13 +23,13 @@ When you create an application using the `rails` command, you are in fact using
```bash
$ rails new myapp
$ cd myapp
-$ rails generate
+$ bin/rails generate
```
You will get a list of all generators that comes with Rails. If you need a detailed description of the helper generator, for example, you can simply do:
```bash
-$ rails generate helper --help
+$ bin/rails generate helper --help
```
Creating Your First Generator
@@ -54,13 +54,13 @@ Our new generator is quite simple: it inherits from `Rails::Generators::Base` an
To invoke our new generator, we just need to do:
```bash
-$ rails generate initializer
+$ bin/rails generate initializer
```
Before we go on, let's see our brand new generator description:
```bash
-$ rails generate initializer --help
+$ bin/rails generate initializer --help
```
Rails is usually able to generate good descriptions if a generator is namespaced, as `ActiveRecord::Generators::ModelGenerator`, but not in this particular case. We can solve this problem in two ways. The first one is calling `desc` inside our generator:
@@ -82,7 +82,7 @@ Creating Generators with Generators
Generators themselves have a generator:
```bash
-$ rails generate generator initializer
+$ bin/rails generate generator initializer
create lib/generators/initializer
create lib/generators/initializer/initializer_generator.rb
create lib/generators/initializer/USAGE
@@ -102,7 +102,7 @@ First, notice that we are inheriting from `Rails::Generators::NamedBase` instead
We can see that by invoking the description of this new generator (don't forget to delete the old generator file):
```bash
-$ rails generate initializer --help
+$ bin/rails generate initializer --help
Usage:
rails generate initializer NAME [options]
```
@@ -130,7 +130,7 @@ end
And let's execute our generator:
```bash
-$ rails generate initializer core_extensions
+$ bin/rails generate initializer core_extensions
```
We can see that now an initializer named core_extensions was created at `config/initializers/core_extensions.rb` with the contents of our template. That means that `copy_file` copied a file in our source root to the destination path we gave. The method `file_name` is automatically created when we inherit from `Rails::Generators::NamedBase`.
@@ -169,7 +169,7 @@ end
Before we customize our workflow, let's first see what our scaffold looks like:
```bash
-$ rails generate scaffold User name:string
+$ bin/rails generate scaffold User name:string
invoke active_record
create db/migrate/20130924151154_create_users.rb
create app/models/user.rb
@@ -224,7 +224,7 @@ If we generate another resource with the scaffold generator, we can see that sty
To demonstrate this, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator within the rails namespace, as this is where rails searches for generators used as hooks:
```bash
-$ rails generate generator rails/my_helper
+$ bin/rails generate generator rails/my_helper
create lib/generators/rails/my_helper
create lib/generators/rails/my_helper/my_helper_generator.rb
create lib/generators/rails/my_helper/USAGE
@@ -248,10 +248,10 @@ end
end
```
-We can try out our new generator by creating a helper for users:
+We can try out our new generator by creating a helper for products:
```bash
-$ rails generate my_helper products
+$ bin/rails generate my_helper products
create app/helpers/products_helper.rb
```
@@ -279,7 +279,7 @@ end
and see it in action when invoking the generator:
```bash
-$ rails generate scaffold Post body:text
+$ bin/rails generate scaffold Post body:text
[...]
invoke my_helper
create app/helpers/posts_helper.rb
@@ -365,7 +365,7 @@ end
Now, if you create a Comment scaffold, you will see that the shoulda generators are being invoked, and at the end, they are just falling back to TestUnit generators:
```bash
-$ rails generate scaffold Comment body:text
+$ bin/rails generate scaffold Comment body:text
invoke active_record
create db/migrate/20130924143118_create_comments.rb
create app/models/comment.rb
@@ -507,7 +507,7 @@ Replaces text inside a file.
gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code'
```
-Regular Expressions can be used to make this method more precise. You can also use append_file and prepend_file in the same way to place code at the beginning and end of a file respectively.
+Regular Expressions can be used to make this method more precise. You can also use `append_file` and `prepend_file` in the same way to place code at the beginning and end of a file respectively.
### `application`
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index c54c9efe94..34ce570545 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -89,9 +89,9 @@ Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose
dollar sign `$` should be run in the command line. Verify that you have a
current version of Ruby installed:
-TIP. A number of tools exist to help you quickly install Ruby and Ruby
+TIP: A number of tools exist to help you quickly install Ruby and Ruby
on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),
-while Mac OS X users can use [Rails One Click](http://railsoneclick.com).
+while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp).
```bash
$ ruby -v
@@ -99,7 +99,7 @@ ruby 2.0.0p353
```
If you don't have Ruby installed have a look at
-[ruby-lang.org](https://www.ruby-lang.org/en/downloads/) for possible ways to
+[ruby-lang.org](https://www.ruby-lang.org/en/installation/) for possible ways to
install Ruby on your platform.
Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows
@@ -122,7 +122,7 @@ To verify that you have everything installed correctly, you should be able to
run the following:
```bash
-$ rails --version
+$ bin/rails --version
```
If it says something like "Rails 4.1.0", you are ready to continue.
@@ -190,7 +190,7 @@ start a web server on your development machine. You can do this by running the
following in the `blog` directory:
```bash
-$ rails server
+$ bin/rails server
```
TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the
@@ -243,7 +243,7 @@ tell it you want a controller called "welcome" with an action called "index",
just like this:
```bash
-$ rails generate controller welcome index
+$ bin/rails generate controller welcome index
```
Rails will create several files and a route for you.
@@ -267,8 +267,9 @@ invoke scss
create app/assets/stylesheets/welcome.css.scss
```
-Most important of these are of course the controller, located at `app/controllers/welcome_controller.rb`
-and the view, located at `app/views/welcome/index.html.erb`.
+Most important of these are of course the controller, located at
+`app/controllers/welcome_controller.rb` and the view, located at
+`app/views/welcome/index.html.erb`.
Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all
of the existing code in the file, and replace it with the following single line
@@ -344,7 +345,7 @@ resource. Here's what `config/routes.rb` should look like after the
_article resource_ is declared.
```ruby
-Blog::Application.routes.draw do
+Rails.application.routes.draw do
resources :articles
@@ -358,7 +359,7 @@ will be seen later, but for now notice that Rails has inferred the
singular form `article` and makes meaningful use of the distinction.
```bash
-$ rake routes
+$ bin/rake routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
@@ -396,7 +397,7 @@ a controller called `ArticlesController`. You can do this by running this
command:
```bash
-$ rails g controller articles
+$ bin/rails g controller articles
```
If you open up the newly generated `app/controllers/articles_controller.rb`
@@ -427,19 +428,22 @@ are generated in Rails they are empty by default, unless you tell it
your wanted actions during the generation process.
To manually define an action inside a controller, all you need to do is to
-define a new method inside the controller.
-Open `app/controllers/articles_controller.rb` and inside the `ArticlesController`
-class, define a `new` method like this:
+define a new method inside the controller. Open
+`app/controllers/articles_controller.rb` and inside the `ArticlesController`
+class, define a `new` method so that the controller now looks like this:
```ruby
-def new
+class ArticlesController < ApplicationController
+ def new
+ end
end
```
With the `new` method defined in `ArticlesController`, if you refresh
<http://localhost:3000/articles/new> you'll see another error:
-![Template is missing for articles/new](images/getting_started/template_is_missing_articles_new.png)
+![Template is missing for articles/new]
+(images/getting_started/template_is_missing_articles_new.png)
You're getting this error now because Rails expects plain actions like this one
to have views associated with them to display their information. With no view
@@ -552,7 +556,7 @@ To see what Rails will do with this, we look back at the output of
`rake routes`:
```bash
-$ rake routes
+$ bin/rake routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
@@ -565,18 +569,18 @@ edit_article GET /articles/:id/edit(.:format) articles#edit
root GET / welcome#index
```
-The `articles_path` helper tells Rails to point the form
-to the URI Pattern associated with the `articles` prefix; and
-the form will (by default) send a `POST` request
-to that route. This is associated with the
-`create` action of the current controller, the `ArticlesController`.
+The `articles_path` helper tells Rails to point the form to the URI Pattern
+associated with the `articles` prefix; and the form will (by default) send a
+`POST` request to that route. This is associated with the `create` action of
+the current controller, the `ArticlesController`.
With the form and its associated route defined, you will be able to fill in the
form and then click the submit button to begin the process of creating a new
article, so go ahead and do that. When you submit the form, you should see a
familiar error:
-![Unknown action create for ArticlesController](images/getting_started/unknown_action_create_for_articles.png)
+![Unknown action create for ArticlesController]
+(images/getting_started/unknown_action_create_for_articles.png)
You now need to create the `create` action within the `ArticlesController` for
this to work.
@@ -585,7 +589,7 @@ this to work.
To make the "Unknown action" go away, you can define a `create` action within
the `ArticlesController` class in `app/controllers/articles_controller.rb`,
-underneath the `new` action:
+underneath the `new` action, as shown:
```ruby
class ArticlesController < ApplicationController
@@ -612,7 +616,7 @@ def create
end
```
-The `render` method here is taking a very simple hash with a key of `text` and
+The `render` method here is taking a very simple hash with a key of `plain` and
value of `params[:article].inspect`. The `params` method is the object which
represents the parameters (or fields) coming in from the form. The `params`
method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
@@ -632,13 +636,13 @@ parameters but nothing in particular is being done with them.
### Creating the Article model
-Models in Rails use a singular name, and their corresponding database tables use
-a plural name. Rails provides a generator for creating models, which
-most Rails developers tend to use when creating new models.
-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. Rails provides a generator for creating models, which most
+Rails developers tend to use when creating new models. To create the new model,
+run this command in your terminal:
```bash
-$ rails generate model Article title:string text:text
+$ bin/rails generate model Article title:string text:text
```
With that command we told Rails that we want a `Article` model, together
@@ -646,26 +650,23 @@ with a _title_ attribute of type string, and a _text_ attribute
of type text. Those attributes are automatically added to the `articles`
table in the database and mapped to the `Article` model.
-Rails responded by creating a bunch of files. For
-now, we're only interested in `app/models/article.rb` and
-`db/migrate/20140120191729_create_articles.rb` (your name could be a bit
-different). The latter is responsible
-for creating the database structure, which is what we'll look at next.
+Rails responded by creating a bunch of files. For now, we're only interested
+in `app/models/article.rb` and `db/migrate/20140120191729_create_articles.rb`
+(your name could be a bit different). The latter is responsible for creating
+the database structure, which is what we'll look at next.
-TIP: Active Record is smart enough to automatically map column names to
-model attributes, which means you don't have to declare attributes
-inside Rails models, as that will be done automatically by Active
-Record.
+TIP: Active Record is smart enough to automatically map column names to model
+attributes, which means you don't have to declare attributes inside Rails
+models, as that will be done automatically by Active Record.
### Running a Migration
-As we've just seen, `rails generate model` created a _database
-migration_ file inside the `db/migrate` directory.
-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.
+As we've just seen, `rails generate model` created a _database migration_ file
+inside the `db/migrate` directory. 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/20140120191729_create_articles.rb` file (remember,
yours will have a slightly different name), here's what you'll find:
@@ -690,13 +691,13 @@ in case you want to reverse it later. When you run this migration it will create
an `articles` table with one string column and a text column. It also creates
two timestamp fields to allow Rails to track article creation and update times.
-TIP: For more information about migrations, refer to [Rails Database
-Migrations](migrations.html).
+TIP: For more information about migrations, refer to [Rails Database Migrations]
+(migrations.html).
At this point, you can use a rake command to run the migration:
```bash
-$ rake db:migrate
+$ bin/rake db:migrate
```
Rails will execute this migration command and tell you it created the Articles
@@ -733,49 +734,47 @@ end
Here's what's going on: every Rails model can be initialized with its
respective attributes, which are automatically mapped to the respective
-database columns. In the first line we do just that
-(remember that `params[:article]` contains the attributes we're interested in).
-Then, `@article.save` is responsible for saving the model in the database.
-Finally, we redirect the user to the `show` action, which we'll define later.
+database columns. In the first line we do just that (remember that
+`params[:article]` contains the attributes we're interested in). Then,
+`@article.save` is responsible for saving the model in the database. Finally,
+we redirect the user to the `show` action, which we'll define later.
-TIP: As we'll see later, `@article.save` returns a boolean indicating
-whether the article was saved or not.
+TIP: As we'll see later, `@article.save` returns a boolean indicating whether
+the article was saved or not.
-If you now go to
-<http://localhost:3000/articles/new> you'll *almost* be able to create an
-article. Try it! You should get an error that looks like this:
+If you now go to <http://localhost:3000/articles/new> you'll *almost* be able
+to create an article. Try it! You should get an error that looks like this:
-![Forbidden attributes for new article](images/getting_started/forbidden_attributes_for_new_article.png)
+![Forbidden attributes for new article]
+(images/getting_started/forbidden_attributes_for_new_article.png)
Rails has several security features that help you write secure applications,
-and you're running into one of them now. This one is called
-`[strong_parameters](http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters)`,
-which requires us to tell Rails exactly which parameters are allowed into
-our controller actions.
-
-Why do you have to bother? The ability to grab and automatically assign
-all controller parameters to your model in one shot makes the programmer's
-job easier, but this convenience also allows malicious use. What if a
-request to the server was crafted to look like a new article form submit
-but also included extra fields with values that violated your applications
-integrity? They would be 'mass assigned' into your model and then into the
-database along with the good stuff - potentially breaking your application
-or worse.
-
-We have to whitelist our controller parameters to prevent wrongful
-mass assignment. In this case, we want to both allow and require the
-`title` and `text` parameters for valid use of `create`. The syntax for
-this introduces `require` and `permit`. The change will involve one line:
+and you're running into one of them now. This one is called `[strong_parameters]
+(http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters)`,
+which requires us to tell Rails exactly which parameters are allowed into our
+controller actions.
+
+Why do you have to bother? The ability to grab and automatically assign all
+controller parameters to your model in one shot makes the programmer's job
+easier, but this convenience also allows malicious use. What if a request to
+the server was crafted to look like a new article form submit but also included
+extra fields with values that violated your applications integrity? They would
+be 'mass assigned' into your model and then into the database along with the
+good stuff - potentially breaking your application or worse.
+
+We have to whitelist our controller parameters to prevent wrongful mass
+assignment. In this case, we want to both allow and require the `title` and
+`text` parameters for valid use of `create`. The syntax for this introduces
+`require` and `permit`. The change will involve one line in the `create` action:
```ruby
@article = Article.new(params.require(:article).permit(:title, :text))
```
-This is often factored out into its own method so it can be reused by
-multiple actions in the same controller, for example `create` and `update`.
-Above and beyond mass assignment issues, the method is often made
-`private` to make sure it can't be called outside its intended context.
-Here is the result:
+This is often factored out into its own method so it can be reused by multiple
+actions in the same controller, for example `create` and `update`. Above and
+beyond mass assignment issues, the method is often made `private` to make sure
+it can't be called outside its intended context. Here is the result:
```ruby
def create
@@ -792,13 +791,14 @@ private
```
TIP: For more information, refer to the reference above and
-[this blog article about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
+[this blog article about Strong Parameters]
+(http://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
### Showing Articles
-If you submit the form again now, Rails will complain about not finding
-the `show` action. That's not very useful though, so let's add the
-`show` action before proceeding.
+If you submit the form again now, Rails will complain about not finding the
+`show` action. That's not very useful though, so let's add the `show` action
+before proceeding.
As we have seen in the output of `rake routes`, the route for `show` action is
as follows:
@@ -813,10 +813,24 @@ parameter, which in our case will be the id of the article.
As we did before, we need to add the `show` action in
`app/controllers/articles_controller.rb` and its respective view.
+NOTE: A frequent practice is to place the standard CRUD actions in each
+controller in the following order: `index`, `show`, `new`, `edit`, `create`, `update`
+and `destroy`. You may use any order you choose, but keep in mind that these
+are public methods; as mentioned earlier in this guide, they must be placed
+before any private or protected method in the controller in order to work.
+
+Given that, let's add the `show` action, as follows:
+
```ruby
-def show
- @article = Article.find(params[:id])
-end
+class ArticlesController < ApplicationController
+ def show
+ @article = Article.find(params[:id])
+ end
+
+ def new
+ end
+
+ # snipped for brevity
```
A couple of things to note. We use `Article.find` to find the article we're
@@ -855,15 +869,27 @@ articles GET /articles(.:format) articles#index
```
Add the corresponding `index` action for that route inside the
-`ArticlesController` in the `app/controllers/articles_controller.rb` file:
+`ArticlesController` in the `app/controllers/articles_controller.rb` file.
+When we write an `index` action, the usual practice is to place it as the
+first method in the controller. Let's do it:
```ruby
-def index
- @articles = Article.all
-end
+class ArticlesController < ApplicationController
+ def index
+ @articles = Article.all
+ end
+
+ def show
+ @article = Article.find(params[:id])
+ end
+
+ def new
+ end
+
+ # snipped for brevity
```
-And then finally, add view for this action, located at
+And then finally, add the view for this action, located at
`app/views/articles/index.html.erb`:
```html+erb
@@ -913,8 +939,8 @@ Let's add links to the other views as well, starting with adding this
This link will allow you to bring up the form that lets you create a new article.
-Also add a link in `app/views/articles/new.html.erb`, underneath the form, to
-go back to the `index` action:
+Now, add another link in `app/views/articles/new.html.erb`, underneath the
+form, to go back to the `index` action:
```erb
<%= form_for :article, url: articles_path do |f| %>
@@ -924,7 +950,7 @@ go back to the `index` action:
<%= link_to 'Back', articles_path %>
```
-Finally, add another link to the `app/views/articles/show.html.erb` template to
+Finally, add a link to the `app/views/articles/show.html.erb` template to
go back to the `index` action as well, so that people who are viewing a single
article can go back and view the whole list again:
@@ -942,9 +968,9 @@ article can go back and view the whole list again:
<%= link_to 'Back', articles_path %>
```
-TIP: If you want to link to an action in the same controller, you don't
-need to specify the `:controller` option, as Rails will use the current
-controller by default.
+TIP: If you want to link to an action in the same controller, you don't need to
+specify the `:controller` option, as Rails will use the current controller by
+default.
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
@@ -1028,17 +1054,21 @@ something went wrong. To do that, you'll modify
```html+erb
<%= form_for :article, url: articles_path do |f| %>
+
<% if @article.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@article.errors.count, "error") %> prohibited
- this article from being saved:</h2>
- <ul>
- <% @article.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1052,6 +1082,7 @@ something went wrong. To do that, you'll modify
<p>
<%= f.submit %>
</p>
+
<% end %>
<%= link_to 'Back', articles_path %>
@@ -1084,12 +1115,27 @@ you attempt to do just that on the new article form
We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating
articles.
-The first step we'll take is adding an `edit` action to the `ArticlesController`.
+The first step we'll take is adding an `edit` action to the `ArticlesController`,
+generally between the `new` and `create` actions, as shown:
```ruby
+def new
+ @article = Article.new
+end
+
def edit
@article = Article.find(params[:id])
end
+
+def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ redirect_to @article
+ else
+ render 'new'
+ end
+end
```
The view will contain a form similar to the one we used when creating
@@ -1100,17 +1146,21 @@ it look as follows:
<h1>Editing article</h1>
<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
+
<% if @article.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@article.errors.count, "error") %> prohibited
- this article from being saved:</h2>
- <ul>
- <% @article.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1124,6 +1174,7 @@ it look as follows:
<p>
<%= f.submit %>
</p>
+
<% end %>
<%= link_to 'Back', articles_path %>
@@ -1136,16 +1187,28 @@ The `method: :patch` option tells Rails that we want this form to be submitted
via the `PATCH` HTTP method which is the HTTP method you're expected to use to
**update** resources according to the REST protocol.
-The first parameter of the `form_tag` can be an object, say, `@article` which would
+The first parameter of `form_for` can be an object, say, `@article` which would
cause the helper to fill in the form with the fields of the object. Passing in a
-symbol (`:article`) with the same name as the instance variable (`@article`) also
-automagically leads to the same behavior. This is what is happening here. More details
-can be found in [form_for documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for).
+symbol (`:article`) with the same name as the instance variable (`@article`)
+also automagically leads to the same behavior. This is what is happening here.
+More details can be found in [form_for documentation]
+(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for).
-Next we need to create the `update` action in
-`app/controllers/articles_controller.rb`:
+Next, we need to create the `update` action in
+`app/controllers/articles_controller.rb`.
+Add it between the `create` action and the `private` method:
```ruby
+def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ redirect_to @article
+ else
+ render 'new'
+ end
+end
+
def update
@article = Article.find(params[:id])
@@ -1187,14 +1250,14 @@ it appear next to the "Show" link:
<th colspan="2"></th>
</tr>
-<% @articles.each do |article| %>
- <tr>
- <td><%= article.title %></td>
- <td><%= article.text %></td>
- <td><%= link_to 'Show', article_path(article) %></td>
- <td><%= link_to 'Edit', edit_article_path(article) %></td>
- </tr>
-<% end %>
+ <% @articles.each do |article| %>
+ <tr>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
+ <td><%= link_to 'Edit', edit_article_path(article) %></td>
+ </tr>
+ <% end %>
</table>
```
@@ -1205,8 +1268,8 @@ bottom of the template:
```html+erb
...
-<%= link_to 'Back', articles_path %>
-| <%= link_to 'Edit', edit_article_path(@article) %>
+<%= link_to 'Back', articles_path %> |
+<%= link_to 'Edit', edit_article_path(@article) %>
```
And here's how our app looks so far:
@@ -1215,10 +1278,10 @@ And here's how our app looks so far:
### Using partials to clean up duplication in views
-Our `edit` page looks very similar to the `new` page, in fact they
-both share the same code for displaying the form. Let's remove some duplication
-by using a view partial. By convention, partial files are prefixed by an
-underscore.
+Our `edit` page looks very similar to the `new` page; in fact, they
+both share the same code for displaying the form. Let's remove this
+duplication by using a view partial. By convention, partial files are
+prefixed by an underscore.
TIP: You can read more about partials in the
[Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
@@ -1228,17 +1291,21 @@ content:
```html+erb
<%= form_for @article do |f| %>
+
<% if @article.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@article.errors.count, "error") %> prohibited
- this article from being saved:</h2>
- <ul>
- <% @article.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
+ <div id="error_explanation">
+ <h2>
+ <%= pluralize(@article.errors.count, "error") %> prohibited
+ this article from being saved:
+ </h2>
+ <ul>
+ <% @article.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
<% end %>
+
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
@@ -1252,6 +1319,7 @@ content:
<p>
<%= f.submit %>
</p>
+
<% end %>
```
@@ -1260,8 +1328,8 @@ The reason we can use this shorter, simpler `form_for` declaration
to stand in for either of the other forms is that `@article` is a *resource*
corresponding to a full set of RESTful routes, and Rails is able to infer
which URI and method to use.
-For more information about this use of `form_for`, see
-[Resource-oriented style](//api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style).
+For more information about this use of `form_for`, see [Resource-oriented style]
+(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style).
Now, let's update the `app/views/articles/new.html.erb` view to use this new
partial, rewriting it completely:
@@ -1302,9 +1370,11 @@ people to craft malicious URLs like this:
<a href='http://example.com/articles/1/destroy'>look at this cat!</a>
```
-We use the `delete` method for destroying resources, and this route is mapped to
-the `destroy` action inside `app/controllers/articles_controller.rb`, which
-doesn't exist yet, but is provided below:
+We use the `delete` method for destroying resources, and this route is mapped
+to the `destroy` action inside `app/controllers/articles_controller.rb`, which
+doesn't exist yet. The `destroy` method is generally the last CRUD action in
+the controller, and like the other public CRUD actions, it must be placed
+before any `private` or `protected` methods. Let's add it:
```ruby
def destroy
@@ -1315,13 +1385,67 @@ def destroy
end
```
+The complete `ArticlesController` in the
+`app/controllers/articles_controller.rb` file should now look like this:
+
+```ruby
+class ArticlesController < ApplicationController
+ def index
+ @articles = Article.all
+ end
+
+ def show
+ @article = Article.find(params[:id])
+ end
+
+ def new
+ @article = Article.new
+ end
+
+ def edit
+ @article = Article.find(params[:id])
+ end
+
+ def create
+ @article = Article.new(article_params)
+
+ if @article.save
+ redirect_to @article
+ else
+ render 'new'
+ end
+ end
+
+ def update
+ @article = Article.find(params[:id])
+
+ if @article.update(article_params)
+ redirect_to @article
+ else
+ render 'edit'
+ end
+ end
+
+ def destroy
+ @article = Article.find(params[:id])
+ @article.destroy
+
+ redirect_to articles_path
+ end
+
+ private
+ def article_params
+ params.require(:article).permit(:title, :text)
+ end
+end
+```
+
You can call `destroy` on Active Record objects when you want to delete
them from the database. Note that we don't need to add a view for this
action since we're redirecting to the `index` action.
Finally, add a 'Destroy' link to your `index` action template
-(`app/views/articles/index.html.erb`) to wrap everything
-together.
+(`app/views/articles/index.html.erb`) to wrap everything together.
```html+erb
<h1>Listing Articles</h1>
@@ -1333,16 +1457,17 @@ together.
<th colspan="3"></th>
</tr>
-<% @articles.each do |article| %>
- <tr>
- <td><%= article.title %></td>
- <td><%= article.text %></td>
- <td><%= link_to 'Show', article_path(article) %></td>
- <td><%= link_to 'Edit', edit_article_path(article) %></td>
- <td><%= link_to 'Destroy', article_path(article),
- method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
-<% end %>
+ <% @articles.each do |article| %>
+ <tr>
+ <td><%= article.title %></td>
+ <td><%= article.text %></td>
+ <td><%= link_to 'Show', article_path(article) %></td>
+ <td><%= link_to 'Edit', edit_article_path(article) %></td>
+ <td><%= link_to 'Destroy', article_path(article),
+ method: :delete,
+ data: { confirm: 'Are you sure?' } %></td>
+ </tr>
+ <% end %>
</table>
```
@@ -1360,9 +1485,8 @@ Without this file, the confirmation dialog box wouldn't appear.
Congratulations, you can now create, show, list, update and destroy
articles.
-TIP: In general, Rails encourages the use of resources objects in place
-of declaring routes manually.
-For more information about routing, see
+TIP: In general, Rails encourages using resources objects instead of
+declaring routes manually. For more information about routing, see
[Rails Routing from the Outside In](routing.html).
Adding a Second Model
@@ -1378,7 +1502,7 @@ the `Article` model. This time we'll create a `Comment` model to hold
reference of article comments. Run this command in your terminal:
```bash
-$ rails generate model Comment commenter:string body:text article:references
+$ bin/rails generate model Comment commenter:string body:text article:references
```
This command will generate four files:
@@ -1426,7 +1550,7 @@ the two models. An index for this association is also created on this column.
Go ahead and run the migration:
```bash
-$ rake db:migrate
+$ bin/rake db:migrate
```
Rails is smart enough to only execute the migrations that have not already been
@@ -1502,7 +1626,7 @@ With the model in hand, you can turn your attention to creating a matching
controller. Again, we'll use the same generator we used before:
```bash
-$ rails generate controller Comments
+$ bin/rails generate controller Comments
```
This creates six files and one empty directory:
@@ -1552,8 +1676,8 @@ So first, we'll wire up the Article show template
</p>
<% end %>
-<%= link_to 'Back', articles_path %>
-| <%= link_to 'Edit', edit_article_path(@article) %>
+<%= link_to 'Back', articles_path %> |
+<%= link_to 'Edit', edit_article_path(@article) %>
```
This adds a form on the `Article` show page that creates a new comment by
@@ -1823,8 +1947,8 @@ database and send us back to the show action for the article.
### Deleting Associated Objects
-If you delete an article then its associated comments will also need to be
-deleted. Otherwise they would simply occupy space in the database. Rails allows
+If you delete an article, 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
Article model, `app/models/article.rb`, as follows:
@@ -1841,21 +1965,21 @@ Security
### Basic Authentication
-If you were to publish your blog online, anybody would be able to add, edit and
+If you were to publish your blog online, anyone would be able to add, edit and
delete articles or delete comments.
Rails provides a very simple HTTP authentication system that will work nicely in
this situation.
-In the `ArticlesController` 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
-`http_basic_authenticate_with` method, allowing access to the requested
+In the `ArticlesController` 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
+`http_basic_authenticate_with` method, which allows access to the requested
action if that method allows it.
To use the authentication system, we specify it at the top of our
-`ArticlesController`, in this case, we want the user to be authenticated on
-every action, except for `index` and `show`, so we write that in
-`app/controllers/articles_controller.rb`:
+`ArticlesController` in `app/controllers/articles_controller.rb`. In our case,
+we want the user to be authenticated on every action except `index` and `show`,
+so we write that:
```ruby
class ArticlesController < ApplicationController
@@ -1879,7 +2003,7 @@ class CommentsController < ApplicationController
def create
@article = Article.find(params[:article_id])
- ...
+ # ...
end
# snipped for brevity
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 6bd033f0de..0eba3af6e8 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -92,7 +92,7 @@ Rails adds all `.rb` and `.yml` files from the `config/locales` directory to you
The default `en.yml` locale in this directory contains a sample pair of translation strings:
-```ruby
+```yaml
en:
hello: "Hello world"
```
@@ -179,7 +179,7 @@ end
# in your /etc/hosts file to try this out locally
def extract_locale_from_tld
parsed_locale = request.host.split('.').last
- I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
+ I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
```
@@ -192,7 +192,7 @@ We can also set the locale from the _subdomain_ in a very similar way:
# in your /etc/hosts file to try this out locally
def extract_locale_from_subdomain
parsed_locale = request.subdomains.first
- I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
+ I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
```
@@ -309,7 +309,7 @@ You most probably have something like this in one of your applications:
```ruby
# config/routes.rb
-Yourapp::Application.routes.draw do
+Rails.application.routes.draw do
root to: "home#index"
end
```
@@ -369,7 +369,7 @@ NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do
So let's add the missing translations into the dictionary files (i.e. do the "localization" part):
-```ruby
+```yaml
# config/locales/en.yml
en:
hello_world: Hello world!
@@ -421,7 +421,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali
And in our pirate translations file let's add a time format (it's already there in Rails' defaults for English):
-```ruby
+```yaml
# config/locales/pirate.yml
pirate:
time:
@@ -680,62 +680,13 @@ NOTE: Automatic conversion to HTML safe translate text is only available from th
![i18n demo html safe](images/i18n/demo_html_safe.png)
-How to Store your Custom Translations
--------------------------------------
-
-The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2]
-
-For example a Ruby Hash providing translations can look like this:
-
-```ruby
-{
- pt: {
- foo: {
- bar: "baz"
- }
- }
-}
-```
-
-The equivalent YAML file would look like this:
-
-```ruby
-pt:
- foo:
- bar: baz
-```
-
-As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz".
-
-Here is a "real" example from the Active Support `en.yml` translations YAML file:
-
-```ruby
-en:
- date:
- formats:
- default: "%Y-%m-%d"
- short: "%b %d"
- long: "%B %d, %Y"
-```
-
-So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`:
-
-```ruby
-I18n.t 'date.formats.short'
-I18n.t 'formats.short', scope: :date
-I18n.t :short, scope: 'date.formats'
-I18n.t :short, scope: [:date, :formats]
-```
-
-Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats.
-
### Translations for Active Record Models
You can use the methods `Model.model_name.human` and `Model.human_attribute_name(attribute)` to transparently look up translations for your model and attribute names.
For example when you add the following translations:
-```ruby
+```yaml
en:
activerecord:
models:
@@ -750,7 +701,7 @@ Then `User.model_name.human` will return "Dude" and `User.human_attribute_name("
You can also set a plural form for model names, adding as following:
-```ruby
+```yaml
en:
activerecord:
models:
@@ -761,6 +712,19 @@ en:
Then `User.model_name.human(count: 2)` will return "Dudes". With `count: 1` or without params will return "Dude".
+In the event you need to access nested attributes within a given model, you should nest these under `model/attribute` at the model level of your translation file:
+
+```yaml
+en:
+ activerecord:
+ attributes:
+ user/gender:
+ female: "Female"
+ male: "Male"
+```
+
+Then `User.human_attribute_name("gender.female")` will return "Female".
+
#### Error Message Scopes
Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account.
@@ -920,6 +884,55 @@ Rails uses fixed strings and other localizations, such as format strings and oth
* `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#L33) scope.
+How to Store your Custom Translations
+-------------------------------------
+
+The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2]
+
+For example a Ruby Hash providing translations can look like this:
+
+```yaml
+{
+ pt: {
+ foo: {
+ bar: "baz"
+ }
+ }
+}
+```
+
+The equivalent YAML file would look like this:
+
+```yaml
+pt:
+ foo:
+ bar: baz
+```
+
+As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz".
+
+Here is a "real" example from the Active Support `en.yml` translations YAML file:
+
+```yaml
+en:
+ date:
+ formats:
+ default: "%Y-%m-%d"
+ short: "%b %d"
+ long: "%B %d, %Y"
+```
+
+So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`:
+
+```ruby
+I18n.t 'date.formats.short'
+I18n.t 'formats.short', scope: :date
+I18n.t :short, scope: 'date.formats'
+I18n.t :short, scope: [:date, :formats]
+```
+
+Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats.
+
Customize your I18n Setup
-------------------------
diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb
index 57c224c165..2fdf18a2e9 100644
--- a/guides/source/index.html.erb
+++ b/guides/source/index.html.erb
@@ -9,6 +9,7 @@ Ruby on Rails Guides
<% content_for :index_section do %>
<div id="subCol">
<dl>
+ <dt></dt>
<dd class="kindle">Rails Guides are also available for <%= link_to 'Kindle', @mobi %>.</dd>
<dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd>
</dl>
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index ec3cec5c6f..00b2761716 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -166,6 +166,7 @@ is called.
COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help)
def run_command!(command)
+ command = parse_command(command)
if COMMAND_WHITELIST.include?(command)
send(command)
else
@@ -178,8 +179,7 @@ With the `server` command, Rails will further run the following code:
```ruby
def set_application_directory!
- Dir.chdir(File.expand_path('../../', APP_PATH)) unless
- File.exist?(File.expand_path("config.ru"))
+ Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end
def server
@@ -187,6 +187,8 @@ def server
require_command!("server")
Rails::Server.new.tap do |server|
+ # We need to require application after the server sets environment,
+ # otherwise the --environment option given to the server won't propagate.
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
@@ -207,6 +209,7 @@ sets up the `Rails::Server` class.
require 'fileutils'
require 'optparse'
require 'action_dispatch'
+require 'rails'
module Rails
class Server < ::Rack::Server
@@ -273,7 +276,7 @@ def parse_options(args)
# http://www.meb.uni-bonn.de/docs/cgi/cl.html
args.clear if ENV.include?("REQUEST_METHOD")
- options.merge! opt_parser.parse! args
+ options.merge! opt_parser.parse!(args)
options[:config] = ::File.expand_path(options[:config])
ENV["RACK_ENV"] = options[:environment]
options
@@ -284,13 +287,16 @@ With the `default_options` set to this:
```ruby
def default_options
+ environment = ENV['RACK_ENV'] || 'development'
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
+
{
- environment: ENV['RACK_ENV'] || "development",
- pid: nil,
- Port: 9292,
- Host: "0.0.0.0",
- AccessLog: [],
- config: "config.ru"
+ :environment => environment,
+ :pid => nil,
+ :Port => 9292,
+ :Host => default_host,
+ :AccessLog => [],
+ :config => "config.ru"
}
end
```
@@ -348,6 +354,7 @@ private
def print_boot_information
...
puts "=> Run `rails server -h` for more startup options"
+ ...
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
end
@@ -434,7 +441,11 @@ The `app` method here is defined like so:
```ruby
def app
- @app ||= begin
+ @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
+end
+...
+private
+ def build_app_and_options_from_config
if !::File.exist? options[:config]
abort "configuration #{options[:config]} not found"
end
@@ -443,7 +454,10 @@ def app
self.options.merge! options
app
end
-end
+
+ def build_app_from_string
+ Rack::Builder.new_from_string(self.options[:builder])
+ end
```
The `options[:config]` value defaults to `config.ru` which contains this:
@@ -459,8 +473,14 @@ run <%= app_const %>
The `Rack::Builder.parse_file` method here takes the content from this `config.ru` file and parses it using this code:
```ruby
-app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
- TOPLEVEL_BINDING, config
+app = new_from_string cfgfile, config
+
+...
+
+def self.new_from_string(builder_script, file="(rackup)")
+ eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
+ TOPLEVEL_BINDING, file, 0
+end
```
The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run:
@@ -473,11 +493,22 @@ require ::File.expand_path('../config/environment', __FILE__)
This file is the common file required by `config.ru` (`rails server`) and Passenger. This is where these two ways to run the server meet; everything before this point has been Rack and Rails setup.
-This file begins with requiring `config/application.rb`.
+This file begins with requiring `config/application.rb`:
+
+```ruby
+require File.expand_path('../application', __FILE__)
+```
### `config/application.rb`
-This file requires `config/boot.rb`, but only if it hasn't been required before, which would be the case in `rails server` but **wouldn't** be the case with Passenger.
+This file requires `config/boot.rb`:
+
+```ruby
+require File.expand_path('../boot', __FILE__)
+```
+
+But only if it hasn't been required before, which would be the case in `rails server`
+but **wouldn't** be the case with Passenger.
Then the fun begins!
@@ -498,11 +529,12 @@ This file is responsible for requiring all the individual frameworks of Rails:
require "rails"
%w(
- active_record
- action_controller
- action_mailer
- rails/test_unit
- sprockets
+ active_record
+ action_controller
+ action_view
+ action_mailer
+ rails/test_unit
+ sprockets
).each do |framework|
begin
require "#{framework}/railtie"
@@ -526,7 +558,7 @@ The rest of `config/application.rb` defines the configuration for the
initialized. When `config/application.rb` has finished loading Rails and defined
the application namespace, we go back to `config/environment.rb`,
where the application is initialized. For example, if the application was called
-`Blog`, here we would find `Blog::Application.initialize!`, which is
+`Blog`, here we would find `Rails.application.initialize!`, which is
defined in `rails/application.rb`
### `railties/lib/rails/application.rb`
@@ -555,7 +587,7 @@ def run_initializers(group=:default, *args)
end
```
-The run_initializers code itself is tricky. What Rails is doing here is
+The `run_initializers` code itself is tricky. What Rails is doing here is
traversing all the class ancestors looking for those that respond to an
`initializers` method. It then sorts the ancestors by name, and runs them.
For example, the `Engine` class will make all the engines available by
@@ -568,7 +600,7 @@ initializers (like building the middleware stack) are run last. The `railtie`
initializers are the initializers which have been defined on the `Rails::Application`
itself and are run between the `bootstrap` and `finishers`.
-After this is done we go back to `Rack::Server`
+After this is done we go back to `Rack::Server`.
### Rack: lib/rack/server.rb
@@ -576,7 +608,11 @@ Last time we left when the `app` method was being defined:
```ruby
def app
- @app ||= begin
+ @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
+end
+...
+private
+ def build_app_and_options_from_config
if !::File.exist? options[:config]
abort "configuration #{options[:config]} not found"
end
@@ -585,7 +621,10 @@ def app
self.options.merge! options
app
end
-end
+
+ def build_app_from_string
+ Rack::Builder.new_from_string(self.options[:builder])
+ end
```
At this point `app` is the Rails app itself (a middleware), and what
@@ -603,7 +642,7 @@ def build_app(app)
end
```
-Remember, `build_app` was called (by wrapped_app) in the last line of `Server#start`.
+Remember, `build_app` was called (by `wrapped_app`) in the last line of `Server#start`.
Here's how it looked like when we left:
```ruby
@@ -611,40 +650,50 @@ server.run wrapped_app, options, &blk
```
At this point, the implementation of `server.run` will depend on the
-server you're using. For example, if you were using Mongrel, here's what
+server you're using. For example, if you were using Puma, here's what
the `run` method would look like:
```ruby
-def self.run(app, options={})
- server = ::Mongrel::HttpServer.new(
- options[:Host] || '0.0.0.0',
- options[:Port] || 8080,
- options[:num_processors] || 950,
- options[:throttle] || 0,
- options[:timeout] || 60)
- # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
- # Use is similar to #run, replacing the app argument with a hash of
- # { path=>app, ... } or an instance of Rack::URLMap.
- if options[:map]
- if app.is_a? Hash
- app.each do |path, appl|
- path = '/'+path unless path[0] == ?/
- server.register(path, Rack::Handler::Mongrel.new(appl))
- end
- elsif app.is_a? URLMap
- app.instance_variable_get(:@mapping).each do |(host, path, appl)|
- next if !host.nil? && !options[:Host].nil? && options[:Host] != host
- path = '/'+path unless path[0] == ?/
- server.register(path, Rack::Handler::Mongrel.new(appl))
- end
- else
- raise ArgumentError, "first argument should be a Hash or URLMap"
- end
- else
- server.register('/', Rack::Handler::Mongrel.new(app))
+...
+DEFAULT_OPTIONS = {
+ :Host => '0.0.0.0',
+ :Port => 8080,
+ :Threads => '0:16',
+ :Verbose => false
+}
+
+def self.run(app, options = {})
+ options = DEFAULT_OPTIONS.merge(options)
+
+ if options[:Verbose]
+ app = Rack::CommonLogger.new(app, STDOUT)
end
+
+ if options[:environment]
+ ENV['RACK_ENV'] = options[:environment].to_s
+ end
+
+ server = ::Puma::Server.new(app)
+ min, max = options[:Threads].split(':', 2)
+
+ puts "Puma #{::Puma::Const::PUMA_VERSION} starting..."
+ puts "* Min threads: #{min}, max threads: #{max}"
+ puts "* Environment: #{ENV['RACK_ENV']}"
+ puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}"
+
+ server.add_tcp_listener options[:Host], options[:Port]
+ server.min_threads = min
+ server.max_threads = max
yield server if block_given?
- server.run.join
+
+ begin
+ server.run.join
+ rescue Interrupt
+ puts "* Gracefully stopping, waiting for requests to finish"
+ server.stop(true)
+ puts "* Goodbye!"
+ end
+
end
```
@@ -654,4 +703,4 @@ the last piece of our journey in the Rails initialization process.
This high level overview will help you understand when your code is
executed and how, and overall become a better Rails developer. If you
still want to know more, the Rails source code itself is probably the
-best place to go next.
+best place to go next. \ No newline at end of file
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 1ac4e7f40c..1005057ca9 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -1,5 +1,4 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
@@ -36,7 +35,6 @@
<li class="more-info"><a href="https://github.com/rails/rails">Code</a></li>
<li class="more-info"><a href="http://rubyonrails.org/screencasts">Screencasts</a></li>
<li class="more-info"><a href="http://rubyonrails.org/documentation">Documentation</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/ecosystem">Ecosystem</a></li>
<li class="more-info"><a href="http://rubyonrails.org/community">Community</a></li>
<li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
</ul>
@@ -78,7 +76,6 @@
</select>
</li>
</ul>
- </div>
</div>
</div>
<hr class="hide" />
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 93729c6f72..8f119f36aa 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -20,7 +20,7 @@ Only the latest release series will receive bug fixes. When enough bugs are
fixed and its deemed worthy to release a new gem, this is the branch it happens
from.
-**Currently included series:** 4.0.z
+**Currently included series:** 4.1.z, 4.0.z
Security Issues
---------------
@@ -35,7 +35,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that
security releases are easy to upgrade to if you're running the latest version
of Rails.
-**Currently included series:** 4.0.z, 3.2.z
+**Currently included series:** 4.1.z, 4.0.z
Severe Security Issues
----------------------
@@ -44,7 +44,7 @@ For severe security issues we will provide new versions as above, and also the
last major release series will receive patches and new versions. The
classification of the security issue is judged by the core team.
-**Currently included series:** 4.0.z, 3.2.z
+**Currently included series:** 4.1.z, 4.0.z, 3.2.z
Unsupported Release Series
--------------------------
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index bfee55a95d..6742c05946 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -121,7 +121,7 @@ Of course, calculating timestamps is no fun, so Active Record provides a
generator to handle making it for you:
```bash
-$ rails generate migration AddPartNumberToProducts
+$ bin/rails generate migration AddPartNumberToProducts
```
This will create an empty but appropriately named migration:
@@ -138,7 +138,7 @@ followed by a list of column names and types then a migration containing the
appropriate `add_column` and `remove_column` statements will be created.
```bash
-$ rails generate migration AddPartNumberToProducts part_number:string
+$ bin/rails generate migration AddPartNumberToProducts part_number:string
```
will generate
@@ -154,7 +154,7 @@ end
If you'd like to add an index on the new column, you can do that as well:
```bash
-$ rails generate migration AddPartNumberToProducts part_number:string:index
+$ bin/rails generate migration AddPartNumberToProducts part_number:string:index
```
will generate
@@ -172,7 +172,7 @@ end
Similarly, you can generate a migration to remove a column from the command line:
```bash
-$ rails generate migration RemovePartNumberFromProducts part_number:string
+$ bin/rails generate migration RemovePartNumberFromProducts part_number:string
```
generates
@@ -188,7 +188,7 @@ end
You are not limited to one magically generated column. For example:
```bash
-$ rails generate migration AddDetailsToProducts part_number:string price:decimal
+$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal
```
generates
@@ -207,7 +207,7 @@ followed by a list of column names and types then a migration creating the table
XXX with the columns listed will be generated. For example:
```bash
-$ rails generate migration CreateProducts name:string part_number:string
+$ bin/rails generate migration CreateProducts name:string part_number:string
```
generates
@@ -231,7 +231,7 @@ Also, the generator accepts column type as `references`(also available as
`belongs_to`). For instance:
```bash
-$ rails generate migration AddUserRefToProducts user:references
+$ bin/rails generate migration AddUserRefToProducts user:references
```
generates
@@ -249,7 +249,7 @@ This migration will create a `user_id` column and appropriate index.
There is also a generator which will produce join tables if `JoinTable` is part of the name:
```bash
-rails g migration CreateJoinTableCustomerProduct customer product
+$ bin/rails g migration CreateJoinTableCustomerProduct customer product
```
will produce the following migration:
@@ -273,7 +273,7 @@ relevant table. If you tell Rails what columns you want, then statements for
adding these columns will also be created. For example, running:
```bash
-$ rails generate model Product name:string description:text
+$ bin/rails generate model Product name:string description:text
```
will create a migration that looks like this
@@ -307,7 +307,7 @@ braces. You can use the following modifiers:
For instance, running:
```bash
-$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
+$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
```
will produce a migration that looks like this
@@ -495,6 +495,7 @@ class ExampleMigration < ActiveRecord::Migration
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
+end
```
Using `reversible` will ensure that the instructions are executed in the
@@ -652,7 +653,7 @@ is the numerical prefix on the migration's filename. For example, to migrate
to version 20080906120000 run:
```bash
-$ rake db:migrate VERSION=20080906120000
+$ bin/rake db:migrate VERSION=20080906120000
```
If version 20080906120000 is greater than the current version (i.e., it is
@@ -669,7 +670,7 @@ mistake in it and wish to correct it. Rather than tracking down the version
number associated with the previous migration you can run:
```bash
-$ rake db:rollback
+$ bin/rake db:rollback
```
This will rollback the latest migration, either by reverting the `change`
@@ -677,7 +678,7 @@ method or by running the `down` method. If you need to undo
several migrations you can provide a `STEP` parameter:
```bash
-$ rake db:rollback STEP=3
+$ bin/rake db:rollback STEP=3
```
will revert the last 3 migrations.
@@ -687,7 +688,7 @@ back up again. As with the `db:rollback` task, you can use the `STEP` parameter
if you need to go more than one version back, for example:
```bash
-$ rake db:migrate:redo STEP=3
+$ bin/rake db:migrate:redo STEP=3
```
Neither of these Rake tasks do anything you could not do with `db:migrate`. They
@@ -717,7 +718,7 @@ the corresponding migration will have its `change`, `up` or `down` method
invoked, for example:
```bash
-$ rake db:migrate:up VERSION=20080906120000
+$ bin/rake db:migrate:up VERSION=20080906120000
```
will run the 20080906120000 migration by running the `change` method (or the
@@ -733,7 +734,7 @@ To run migrations against another environment you can specify it using the
migrations against the `test` environment you could run:
```bash
-$ rake db:migrate RAILS_ENV=test
+$ bin/rake db:migrate RAILS_ENV=test
```
### Changing the Output of Running Migrations
@@ -901,6 +902,11 @@ schema into a RDBMS other than the one used to create it.
Because schema dumps are the authoritative source for your database schema, it
is strongly recommended that you check them into source control.
+`db/schema.rb` contains the current version number of the database. This
+ensures conflicts are going to happen in the case of a merge where both
+branches touched the schema. When that happens, solve conflicts manually,
+keeping the highest version number of the two.
+
Active Record and Referential Integrity
---------------------------------------
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index 855fab18e3..4f0634d955 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -17,9 +17,9 @@ Model setup
To be able to use the nested model functionality in your forms, the model will need to support some basic operations.
-First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be build.
+First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built.
-If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded.
+If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded.
Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`.
@@ -220,6 +220,6 @@ As you can see it has generated 2 `project name` inputs, one for each new `proje
You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance.
-NOTE: The reason that `fields_for` constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep.
+NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep.
TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example.
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index fe4215839f..a35648d341 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -39,13 +39,13 @@ to run integration tests using a dummy Rails application. Create your
plugin with the command:
```bash
-$ rails plugin new yaffle
+$ bin/rails plugin new yaffle
```
See usage and options by asking for help:
```bash
-$ rails plugin --help
+$ bin/rails plugin --help
```
Testing Your Newly Generated Plugin
@@ -124,7 +124,7 @@ To test that your method does what it says it does, run the unit tests with `rak
To see this in action, change to the test/dummy directory, fire up a console and start squawking:
```bash
-$ rails console
+$ bin/rails console
>> "Hello World".to_squawk
=> "squawk! Hello World"
```
@@ -214,8 +214,8 @@ test/dummy directory:
```bash
$ cd test/dummy
-$ rails generate model Hickwall last_squawk:string
-$ rails generate model Wickwall last_squawk:string last_tweet:string
+$ bin/rails generate model Hickwall last_squawk:string
+$ bin/rails generate model Wickwall last_squawk:string last_tweet:string
```
Now you can create the necessary database tables in your testing database by navigating to your dummy app
@@ -223,7 +223,7 @@ and migrating the database. First, run:
```bash
$ cd test/dummy
-$ rake db:migrate
+$ bin/rake db:migrate
```
While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act
@@ -433,7 +433,7 @@ Once your README is solid, go through and add rdoc comments to all of the method
Once your comments are good to go, navigate to your plugin directory and run:
```bash
-$ rake rdoc
+$ bin/rake rdoc
```
### References
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index e4222e1283..0bd608c007 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -23,8 +23,8 @@ $ rails new blog -m http://example.com/template.rb
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.
```bash
-$ rake rails:template LOCATION=~/template.rb
-$ rake rails:template LOCATION=http://example.com/template.rb
+$ bin/rake rails:template LOCATION=~/template.rb
+$ bin/rake rails:template LOCATION=http://example.com/template.rb
```
Template API
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 9c92cf3aea..9053f31b8e 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -27,10 +27,9 @@ Rails on Rack
### Rails Application's Rack Object
-`ApplicationName::Application` is the primary Rack application object of a Rails
+`Rails.application` is the primary Rack application object of a Rails
application. Any Rack compliant web server should be using
-`ApplicationName::Application` object to serve a Rails
-application. `Rails.application` refers to the same application object.
+`Rails.application` object to serve a Rails application.
### `rails server`
@@ -112,7 +111,7 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`,
Rails has a handy rake task for inspecting the middleware stack in use:
```bash
-$ rake middleware
+$ bin/rake middleware
```
For a freshly generated Rails application, this might produce something like:
@@ -141,7 +140,7 @@ use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
-run MyApp::Application.routes
+run Rails.application.routes
```
The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below.
@@ -195,13 +194,13 @@ And now if you inspect the middleware stack, you'll find that `Rack::Lock` is
not a part of it.
```bash
-$ rake middleware
+$ bin/rake middleware
(in /Users/lifo/Rails/blog)
use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
use Rack::Runtime
...
-run Blog::Application.routes
+run Rails.application.routes
```
If you want to remove session related middleware, do the following:
diff --git a/guides/source/routing.md b/guides/source/routing.md
index eef618f28d..314dbffae2 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -662,7 +662,7 @@ get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/
`:constraints` takes regular expressions with the restriction that regexp anchors can't be used. For example, the following route will not work:
```ruby
-get '/:id', to: 'posts#show', constraints: {id: /^\d/}
+get '/:id', to: 'posts#show', constraints: { id: /^\d/ }
```
However, note that you don't need to use anchors because all routes are anchored at the start.
@@ -676,12 +676,12 @@ get '/:username', to: 'users#show'
### Request-Based Constraints
-You can also constrain a route based on any method on the <a href="action_controller_overview.html#the-request-object">Request</a> object that returns a `String`.
+You can also constrain a route based on any method on the [Request object](action_controller_overview.html#the-request-object) that returns a `String`.
You specify a request-based constraint the same way that you specify a segment constraint:
```ruby
-get 'photos', constraints: {subdomain: 'admin'}
+get 'photos', constraints: { subdomain: 'admin' }
```
You can also specify constraints in a block form:
@@ -694,6 +694,8 @@ namespace :admin do
end
```
+NOTE: Request constraints work by calling a method on the [Request object](action_controller_overview.html#the-request-object) with the same name as the hash key and then compare the return value with the hash value. Therefore, constraint values should match the corresponding Request object method return type. For example: `constraints: { subdomain: 'api' }` will match an `api` subdomain as expected, however using a symbol `constraints: { subdomain: :api }` will not, because `request.subdomain` returns `'api'` as a String.
+
### Advanced Constraints
If you have a more advanced constraint, you can provide an object that responds to `matches?` that Rails should use. Let's say you wanted to route all users on a blacklist to the `BlacklistController`. You could do:
@@ -709,7 +711,7 @@ class BlacklistConstraint
end
end
-TwitterClone::Application.routes.draw do
+Rails.application.routes.draw do
get '*path', to: 'blacklist#index',
constraints: BlacklistConstraint.new
end
@@ -718,7 +720,7 @@ end
You can also specify constraints as a lambda:
```ruby
-TwitterClone::Application.routes.draw do
+Rails.application.routes.draw do
get '*path', to: 'blacklist#index',
constraints: lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) }
end
@@ -781,8 +783,8 @@ get '/stories/:name', to: redirect('/posts/%{name}')
You can also provide a block to redirect, which receives the symbolized path parameters and the request object:
```ruby
-get '/stories/:name', to: redirect {|path_params, req| "/posts/#{path_params[:name].pluralize}" }
-get '/stories', to: redirect {|path_params, req| "/posts/#{req.subdomain}" }
+get '/stories/:name', to: redirect { |path_params, req| "/posts/#{path_params[:name].pluralize}" }
+get '/stories', to: redirect { |path_params, req| "/posts/#{req.subdomain}" }
```
Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
@@ -791,7 +793,7 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl
### Routing to Rack Applications
-Instead of a String like `'posts#index'`, which corresponds to the `index` action in the `PostsController`, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher:
+Instead of a String like `'posts#index'`, which corresponds to the `index` action in the `PostsController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher:
```ruby
match '/application.js', to: Sprockets, via: :all
@@ -877,7 +879,7 @@ a warning.
You can use the `:constraints` option to specify a required format on the implicit `id`. For example:
```ruby
-resources :photos, constraints: {id: /[A-Z][A-Z][0-9]+/}
+resources :photos, constraints: { id: /[A-Z][A-Z][0-9]+/ }
```
This declaration constrains the `:id` parameter to match the supplied regular expression. So, in this case, the router would no longer match `/photos/1` to this route. Instead, `/photos/RR27` would match.
@@ -917,7 +919,7 @@ will recognize incoming paths beginning with `/photos` and route the requests to
### Overriding the `new` and `edit` Segments
-The `:path_names` option lets you override the automatically-generated "new" and "edit" segments in paths:
+The `:path_names` option lets you override the automatically-generated `new` and `edit` segments in paths:
```ruby
resources :photos, path_names: { new: 'make', edit: 'change' }
@@ -952,7 +954,7 @@ end
resources :photos
```
-This will provide route helpers such as `admin_photos_path`, `new_admin_photo_path` etc.
+This will provide route helpers such as `admin_photos_path`, `new_admin_photo_path`, etc.
To prefix a group of route helpers, use `:as` with `scope`:
@@ -980,7 +982,7 @@ This will provide you with URLs such as `/bob/posts/1` and will allow you to ref
### Restricting the Routes Created
-By default, Rails creates routes for the seven default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the `:only` and `:except` options to fine-tune this behavior. The `:only` option tells Rails to create only the specified routes:
+By default, Rails creates routes for the seven default actions (`index`, `show`, `new`, `create`, `edit`, `update`, and `destroy`) for every RESTful route in your application. You can use the `:only` and `:except` options to fine-tune this behavior. The `:only` option tells Rails to create only the specified routes:
```ruby
resources :photos, only: [:index, :show]
@@ -1070,7 +1072,7 @@ edit_user GET /users/:id/edit(.:format) users#edit
You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable:
```bash
-$ CONTROLLER=users rake routes
+$ CONTROLLER=users bin/rake routes
```
TIP: You'll find that the output from `rake routes` is much more readable if you widen your terminal window until the output lines don't wrap.
diff --git a/guides/source/security.md b/guides/source/security.md
index 9603fb4a4d..75d8c8e4c8 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -17,7 +17,7 @@ After reading this guide, you will know:
Introduction
------------
-Web application frameworks are made to help developers build 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 build 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.
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).
@@ -25,7 +25,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). It is done manually because that's how you find the nasty logical security problems.
Sessions
--------
@@ -60,7 +60,7 @@ Many web applications have an authentication system: a user provides a user name
Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
-* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
+* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
```ruby
config.force_ssl = true
@@ -135,8 +135,8 @@ NOTE: _Apart from stealing a user's session id, the attacker may fix a session i
This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works:
* The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image).
-* They possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore they access the web application from time to time in order to keep the session alive.
-* Now the attacker will force the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on.
+* They maintain the session by accessing the web application periodically in order to keep an expiring session alive.
+* The attacker forces the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: `<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>`. Read more about XSS and injection later on.
* The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id.
* As the new trap session is unused, the web application will require the user to authenticate.
* From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack.
@@ -151,7 +151,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec
reset_session
```
-If you use the popular RestfulAuthentication plugin for user management, add reset\_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_.
+If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_.
Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way.
@@ -198,7 +198,7 @@ In the <a href="#sessions">session chapter</a> you have learned that most Rails
It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post or email.
-CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work - _CSRF is an important security issue_.
+CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in many security contract works - _CSRF is an important security issue_.
### CSRF Countermeasures
@@ -239,24 +239,23 @@ Or the attacker places the code into the onmouseover event handler of an image:
There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request.
-To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller:
+To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications:
```ruby
-protect_from_forgery
+protect_from_forgery with: :exception
```
-This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, the session will be reset.
+This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown.
It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
```ruby
-def handle_unverified_request
- super
- sign_out_user # Example method that will destroy the user cookies.
+rescue_from ActionController::InvalidAuthenticityToken do |exception|
+ sign_out_user # Example method that will destroy the user cookies
end
```
-The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present on a non-GET request.
+The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present or is incorrect on a non-GET request.
Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.
@@ -314,7 +313,7 @@ def sanitize_filename(filename)
end
```
-A significant disadvantage of synchronous processing of file uploads (as the attachment\_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server.
+A significant disadvantage of synchronous processing of file uploads (as the attachment_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server.
The solution to this is best to _process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background.
@@ -375,7 +374,7 @@ For _countermeasures against CSRF in administration interfaces and Intranet appl
The common admin interface works like this: it's located at www.example.com/admin, may be accessed only if the admin flag is set in the User model, re-displays user input and allows the admin to delete/add/edit whatever data desired. Here are some thoughts about this:
-* It is very important to _think about the worst case_: What if someone really got hold of my cookie or user credentials. You could _introduce roles_ for the admin interface to limit the possibilities of the attacker. Or how about _special login credentials_ for the admin interface, other than the ones used for the public part of the application. Or a _special password for very serious actions_?
+* It is very important to _think about the worst case_: What if someone really got hold of your cookies or user credentials. You could _introduce roles_ for the admin interface to limit the possibilities of the attacker. Or how about _special login credentials_ for the admin interface, other than the ones used for the public part of the application. Or a _special password for very serious actions_?
* Does the admin really have to access the interface from everywhere in the world? Think about _limiting the login to a bunch of source IP addresses_. Examine request.remote_ip to find out about the user's IP address. This is not bullet-proof, but a great barrier. Remember that there might be a proxy in use, though.
@@ -407,7 +406,7 @@ If the parameter was nil, the resulting SQL query will be
SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1
```
-And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [my blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
+And thus it found the first user in the database, returned it and logged them in. You can find out more about it in [this blog post](http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/). _It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
### Brute-Forcing Accounts
@@ -733,7 +732,7 @@ Imagine a blacklist deletes "script" from the user input. Now the attacker injec
strip_tags("some<<b>script>alert('hello')<</b>/script>")
```
-This returned "some&lt;script&gt;alert('hello')&lt;/script&gt;", which makes an attack work. That's why I vote for a whitelist approach, using the updated Rails 2 method sanitize():
+This returned "some&lt;script&gt;alert('hello')&lt;/script&gt;", which makes an attack work. That's why a whitelist approach is better, using the updated Rails 2 method sanitize():
```ruby
tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
@@ -813,7 +812,7 @@ The [moz-binding](http://www.securiteam.com/securitynews/5LP051FHPE.html) CSS pr
#### Countermeasures
-This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, I am not aware of a whitelist CSS filter. _If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application_. Use Rails' `sanitize()` method as a model for a whitelist CSS filter, if you really need one.
+This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, it may be hard to find a good whitelist CSS filter. _If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application_. Use Rails' `sanitize()` method as a model for a whitelist CSS filter, if you really need one.
### Textile Injection
diff --git a/guides/source/testing.md b/guides/source/testing.md
index aa37115d14..9fc866fff3 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -134,14 +134,14 @@ Unit Testing your Models
In Rails, models tests are what you write to test your models.
-For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. I will be using examples from this generated code and will be supplementing it with additional examples where necessary.
+For this guide we will be using Rails _scaffolding_. It will create the model, a migration, controller and views for the new resource in a single operation. It will also create a full test suite following Rails best practices. We will be using examples from this generated code and will be supplementing it with additional examples where necessary.
NOTE: For more information on Rails <i>scaffolding</i>, refer to [Getting Started with Rails](getting_started.html)
When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder:
```bash
-$ rails generate scaffold post title:string body:text
+$ bin/rails generate scaffold post title:string body:text
...
create app/models/post.rb
create test/models/post_test.rb
@@ -220,7 +220,7 @@ In order to run your tests, your test database will need to have the current str
Running a test is as simple as invoking the file containing the test cases through `rake test` command.
```bash
-$ rake test test/models/post_test.rb
+$ bin/rake test test/models/post_test.rb
.
Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
@@ -231,7 +231,7 @@ Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
You can also run a particular test method from the test case by running the test and providing the `test method name`.
```bash
-$ rake test test/models/post_test.rb test_the_truth
+$ bin/rake test test/models/post_test.rb test_the_truth
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
@@ -255,7 +255,7 @@ end
Let us run this newly added test.
```bash
-$ rake test test/models/post_test.rb test_should_not_save_post_without_title
+$ bin/rake test test/models/post_test.rb test_should_not_save_post_without_title
F
Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.
@@ -295,7 +295,7 @@ end
Now the test should pass. Let us verify by running the test again:
```bash
-$ rake test test/models/post_test.rb test_should_not_save_post_without_title
+$ bin/rake test test/models/post_test.rb test_should_not_save_post_without_title
.
Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
@@ -320,7 +320,7 @@ end
Now you can see even more output in the console from running the tests:
```bash
-$ rake test test/models/post_test.rb test_should_report_error
+$ bin/rake test test/models/post_test.rb test_should_report_error
E
Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.
@@ -345,7 +345,7 @@ backtrace. simply set the `BACKTRACE` environment variable to enable this
behavior:
```bash
-$ BACKTRACE=1 rake test test/models/post_test.rb
+$ BACKTRACE=1 bin/rake test test/models/post_test.rb
```
### What to Include in Your Unit Tests
@@ -653,7 +653,7 @@ Integration tests are used to test the interaction among any number of controlle
Unlike Unit and Functional tests, integration tests have to be explicitly created under the 'test/integration' folder within your application. Rails provides a generator to create an integration test skeleton for you.
```bash
-$ rails generate integration_test user_flows
+$ bin/rails generate integration_test user_flows
exists test/integration/
create test/integration/user_flows_test.rb
```
@@ -796,7 +796,7 @@ when you initiate a Rails project.
Brief Note About `MiniTest`
-----------------------------
-Ruby ships with a boat load of libraries. Ruby 1.8 provides `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing
+Ruby ships with a vast Standard Library for all common use-cases including testing. Ruby 1.8 provided `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing
us to use all of the basic assertions in our tests.
Ruby 1.9 introduced `MiniTest`, an updated version of `Test::Unit` which provides a backwards compatible API for `Test::Unit`. You could also use `MiniTest` in Ruby 1.8 by installing the `minitest` gem.
@@ -1011,7 +1011,7 @@ located under the `test/helpers` directory. Rails provides a generator which
generates both the helper and the test file:
```bash
-$ rails generate helper User
+$ bin/rails generate helper User
create app/helpers/user_helper.rb
invoke test_unit
create test/helpers/user_helper_test.rb
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 88c9981dbb..b0543d8d20 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -25,8 +25,6 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp
Upgrading from Rails 4.0 to Rails 4.1
-------------------------------------
-NOTE: This section is a work in progress.
-
### CSRF protection from remote `<script>` tags
Or, "whaaat my tests are failing!!!?"
@@ -79,11 +77,14 @@ secrets, you need to:
secret_key_base:
production:
- secret_key_base:
+ secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
```
-2. Copy the existing `secret_key_base` from the `secret_token.rb` initializer to
- `secrets.yml` under the `production` section.
+2. Use your existing `secret_key_base` from the `secret_token.rb` initializer to
+ set the SECRET_KEY_BASE environment variable for whichever users run the Rails
+ app in production mode. Alternately, you can simply copy the existing
+ `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml`
+ under the `production` section, replacing '<%= ENV["SECRET_KEY_BASE"] %>'.
3. Remove the `secret_token.rb` initializer.
@@ -392,6 +393,14 @@ start using the more precise `:plain:`, `:html`, and `:body` options instead.
Using `render :text` may pose a security risk, as the content is sent as
`text/html`.
+### PostgreSQL json and hstore datatypes
+
+Rails 4.1 will map `json` and `hstore` columns to a string-keyed Ruby `Hash`.
+In earlier versions a `HashWithIndifferentAccess` was used. This means that
+symbol access is no longer supported. This is also the case for
+`store_accessors` based on top of `json` or `hstore` columns. Make sure to use
+string keys consistently.
+
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------
@@ -463,7 +472,7 @@ being used, you can update your form to use the `PUT` method instead:
<%= form_for [ :update_name, @user ], method: :put do |f| %>
```
-For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/)
+For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/)
on the Rails blog.
#### A note about media types
@@ -882,7 +891,7 @@ AppName::Application.config.session_store :cookie_store, key: 'SOMETHINGNEW'
or
```bash
-$ rake db:sessions:clear
+$ bin/rake db:sessions:clear
```
### Remove :cache and :concat options in asset helpers references in views
diff --git a/guides/w3c_validator.rb b/guides/w3c_validator.rb
index 6ef3df45a9..71f044b9c4 100644
--- a/guides/w3c_validator.rb
+++ b/guides/w3c_validator.rb
@@ -60,6 +60,8 @@ module RailsGuides
def guides_to_validate
guides = Dir["./output/*.html"]
guides.delete("./output/layout.html")
+ guides.delete("./output/_license.html")
+ guides.delete("./output/_welcome.html")
ENV.key?('ONLY') ? select_only(guides) : guides
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index afbebf5b95..ba6a0feeef 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,28 @@
+* Default `config.assets.digest` to `true` in development.
+
+ *Dan Kang*
+
+* Load database configuration from the first
+ database.yml available in paths.
+
+ *Pier-Olivier Thibault*
+
+* Reading name and email from git for plugin gemspec.
+
+ Fixes #9589.
+
+ *Arun Agrawal*, *Abd ar-Rahman Hamidi*, *Roman Shmatov*
+
+* Fix `console` and `generators` blocks defined at different environments.
+
+ Fixes #14748.
+
+ *Rafael Mendonça França*
+
+* Move configuration of asset precompile list and version to an initializer.
+
+ *Matthew Draper*
+
* Do not set the Rails environment to test by default when using test_unit Railtie.
*Konstantin Shabanov*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index dd650e9631..2fde974732 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -87,7 +87,7 @@ module Rails
class << self
def inherited(base)
super
- Rails.application ||= base.instance
+ base.instance
end
# Makes the +new+ method public.
@@ -117,6 +117,8 @@ module Rails
@railties = nil
@message_verifiers = {}
+ Rails.application ||= self
+
add_lib_to_load_path!
ActiveSupport.run_load_hooks(:before_configuration, self)
@@ -151,14 +153,13 @@ module Rails
def key_generator
# number of iterations selected based on consultation with the google security
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
- @caching_key_generator ||= begin
+ @caching_key_generator ||=
if secrets.secret_key_base
key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000)
ActiveSupport::CachingKeyGenerator.new(key_generator)
else
ActiveSupport::LegacyKeyGenerator.new(config.secret_token)
end
- end
end
# Returns a message verifier object.
@@ -230,6 +231,18 @@ module Rails
self.class.runner(&blk)
end
+ # Sends any console called in the instance of a new application up
+ # to the +console+ method defined in Rails::Railtie.
+ def console(&blk)
+ self.class.console(&blk)
+ end
+
+ # Sends any generators called in the instance of a new application up
+ # to the +generators+ method defined in Rails::Railtie.
+ def generators(&blk)
+ self.class.generators(&blk)
+ end
+
# Sends the +isolate_namespace+ method up to the class method.
def isolate_namespace(mod)
self.class.isolate_namespace(mod)
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 4c449d2c57..5e8f4de847 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -92,7 +92,7 @@ module Rails
# Loads and returns the entire raw configuration of database from
# values stored in `config/database.yml`.
def database_configuration
- yaml = Pathname.new(paths["config/database"].first || "")
+ yaml = Pathname.new(paths["config/database"].existent.first || "")
config = if yaml.exist?
require "yaml"
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index f6bdf129d6..555d8f31e1 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -18,7 +18,14 @@ module Rails
opt.on("-e", "--environment=name", String,
"Specifies the environment to run this console under (test/development/production).",
"Default: development") { |v| options[:environment] = v.strip }
- opt.on("--debugger", 'Enable the debugger.') { |v| options[:debugger] = v }
+ opt.on("--debugger", 'Enable the debugger.') do |v|
+ if RUBY_VERSION < '2.0.0'
+ options[:debugger] = v
+ else
+ puts "=> Notice: debugger option is ignored since ruby 2.0 and " \
+ "it will be removed in future versions"
+ end
+ end
opt.parse!(arguments)
end
@@ -69,12 +76,25 @@ module Rails
Rails.env = environment
end
- def debugger?
- options[:debugger]
+ if RUBY_VERSION < '2.0.0'
+ def debugger?
+ options[:debugger]
+ end
+
+ def require_debugger
+ require 'debugger'
+ puts "=> Debugger enabled"
+ rescue LoadError
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
+ exit(1)
+ end
end
def start
- require_debugger if debugger?
+ if RUBY_VERSION < '2.0.0'
+ require_debugger if debugger?
+ end
+
set_environment! if environment?
if sandbox?
@@ -89,13 +109,5 @@ module Rails
end
console.start
end
-
- def require_debugger
- require 'debugger'
- puts "=> Debugger enabled"
- rescue LoadError
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again."
- exit(1)
- end
end
end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index f6d8aec30d..1a2613a8d0 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -20,7 +20,7 @@ module Rails
ENV['RAILS_ENV'] = options[:environment] || environment
case config["adapter"]
- when /^mysql/
+ when /^(jdbc)?mysql/
args = {
'host' => '--host',
'port' => '--port',
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index fec4962fb5..6146b6c1db 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -18,7 +18,14 @@ module Rails
opts.on("-c", "--config=file", String,
"Use custom rackup configuration file") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true }
- opts.on("-u", "--debugger", "Enable the debugger") { options[:debugger] = true }
+ opts.on("-u", "--debugger", "Enable the debugger") do
+ if RUBY_VERSION < '2.0.0'
+ options[:debugger] = true
+ else
+ puts "=> Notice: debugger option is ignored since ruby 2.0 and " \
+ "it will be removed in future versions"
+ end
+ end
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
@@ -75,7 +82,9 @@ module Rails
def middleware
middlewares = []
- middlewares << [Rails::Rack::Debugger] if options[:debugger]
+ if RUBY_VERSION < '2.0.0'
+ middlewares << [Rails::Rack::Debugger] if options[:debugger]
+ end
middlewares << [::Rack::ContentLength]
# FIXME: add Rack::Lock in the case people are using webrick.
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index c066f748ee..569afe8104 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -79,7 +79,6 @@ module Rails
end
def initialize(*args)
- @original_wd = Dir.pwd
@gem_filter = lambda { |gem| true }
@extra_entries = []
super
@@ -203,7 +202,8 @@ module Rails
[GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
GemfileEntry.github('arel', 'rails/arel')]
elsif options.edge?
- [GemfileEntry.github('rails', 'rails/rails')]
+ [GemfileEntry.github('rails', 'rails/rails'),
+ GemfileEntry.github('arel', 'rails/arel')]
else
[GemfileEntry.version('rails',
Rails::VERSION::STRING,
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 67bab96a22..9af6435f23 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -83,7 +83,7 @@ module Rails
#
# The first and last part used to find the generator to be invoked are
# guessed based on class invokes hook_for, as noticed in the example above.
- # This can be customized with two options: :base and :as.
+ # This can be customized with two options: :in and :as.
#
# Let's suppose you are creating a generator that needs to invoke the
# controller generator from test unit. Your first attempt is:
@@ -108,7 +108,7 @@ module Rails
# "test_unit:controller", "test_unit"
#
# Similarly, if you want it to also lookup in the rails namespace, you just
- # need to provide the :base value:
+ # need to provide the :in value:
#
# class AwesomeGenerator < Rails::Generators::Base
# hook_for :test_framework, in: :rails, as: :controller
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 69c10efa47..da99e74435 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -4,8 +4,8 @@
<h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
<ul>
- <%% @<%= singular_table_name %>.errors.full_messages.each do |msg| %>
- <li><%%= msg %></li>
+ <%% @<%= singular_table_name %>.errors.full_messages.each do |message| %>
+ <li><%%= message %></li>
<%% end %>
</ul>
</div>
@@ -21,13 +21,8 @@
<%%= f.label :password_confirmation %><br>
<%%= f.password_field :password_confirmation %>
<% else -%>
- <%- if attribute.reference? -%>
<%%= f.label :<%= attribute.column_name %> %><br>
<%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %>
- <%- else -%>
- <%%= f.label :<%= attribute.name %> %><br>
- <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
- <%- end -%>
<% end -%>
</div>
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index abf6909a7f..188e62b6c8 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -86,6 +86,16 @@ module Rails
end
end
+ def config_when_updating
+ cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb')
+
+ config
+
+ unless cookie_serializer_config_exist
+ gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal'
+ end
+ end
+
def database_yml
template "config/databases/#{options[:database]}.yml", "config/database.yml"
end
@@ -188,6 +198,11 @@ module Rails
build(:config)
end
+ def update_config_files
+ build(:config_when_updating)
+ end
+ remove_task :update_config_files
+
def create_boot_file
template "config/boot.rb"
end
@@ -231,6 +246,12 @@ module Rails
end
end
+ def delete_assets_initializer_skipping_sprockets
+ if options[:skip_sprockets]
+ remove_file 'config/initializers/assets.rb'
+ end
+ end
+
def finish_template
build(:leftovers)
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index a9b6787894..448b6f4845 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -23,11 +23,15 @@ source 'https://rubygems.org'
# gem 'capistrano-rails', group: :development
<% unless defined?(JRUBY_VERSION) -%>
-# Use debugger
+# To use a debugger
+ <%- if RUBY_VERSION < '2.0.0' -%>
# gem 'debugger', group: [:development, :test]
+ <%- else -%>
+# gem 'byebug', group: [:development, :test]
+ <%- end -%>
<% end -%>
<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince/) -%>
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
-gem 'tzinfo-data', platforms: [:mingw, :mswin]
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
index 138e3a8664..34fc0e3465 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
@@ -23,7 +23,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="frontbase://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
index 2cdb592eeb..d088dd62bf 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
@@ -61,7 +61,27 @@ test:
<<: *default
database: <%= app_name[0,4] %>_tst
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="ibm-db://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
index cefd30d519..db0a429753 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
@@ -53,7 +53,16 @@ test:
<<: *default
url: jdbc:db://localhost/<%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ url: jdbc:db://localhost/<%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
index d31761349c..acb93939e1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
@@ -26,6 +26,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
-production: <%%= ENV["DATABASE_URL"] %>
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="mysql://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
+production:
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
index 0d248dc197..9e99264d33 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
@@ -42,7 +42,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
index 66eba3bf0d..28c36eb82f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
@@ -18,7 +18,6 @@ test:
<<: *default
database: db/test.sqlite3
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: db/production.sqlite3
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index d618fc28a6..4b2e6646c7 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -32,11 +32,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Avoid production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
#
-# Example:
-# mysql2://myuser:mypass@localhost/somedatabase
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index d469ec0f99..10ab4c02e2 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -32,7 +32,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="oracle://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index 93f48656b2..feb25bbc6b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -59,11 +59,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
#
-# Example:
-# postgres://myuser:mypass@localhost/somedatabase
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
index 71934bb48c..1c1a37ca8d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
@@ -20,13 +20,6 @@ test:
<<: *default
database: db/test.sqlite3
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
-#
-# Examples:
-# sqlite3::memory:
-# sqlite3:db/production.sqlite3
-# sqlite3:/full/path/to/database.sqlite3
-#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: db/production.sqlite3
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
index aa960e493e..30b0df34a8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
@@ -42,7 +42,27 @@ test:
<<: *default
database: <%= app_name %>_test
-# Do not keep production credentials in the repository,
-# instead read the configuration from the environment.
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="sqlserver://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%%= ENV['DATABASE_URL'] %>
+#
production:
- url: <%%= ENV["DATABASE_URL"] %>
+ <<: *default
+ database: <%= app_name %>_production
+ username: <%= app_name %>
+ password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %>
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 de12565a73..bbb409616d 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
@@ -30,6 +30,9 @@ Rails.application.configure do
# number of complex assets.
config.assets.debug = true
+ # Generate digests for assets URLs.
+ config.assets.digest = true
+
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
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 b789ed9a94..9ed71687ea 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
@@ -33,12 +33,7 @@ Rails.application.configure do
# Generate digests for assets URLs.
config.assets.digest = true
- # Version of your assets, change this if you want to expire all your assets.
- config.assets.version = '1.0'
-
- # Precompile additional assets.
- # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
- # config.assets.precompile += %w( search.js )
+ # `config.assets.precompile` has moved to config/initializers/assets.rb
<%- end -%>
# Specifies the header that your server uses for sending files.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
new file mode 100644
index 0000000000..d2f4ec33a6
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = '1.0'
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
+# Rails.application.config.assets.precompile += %w( search.js )
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
index 7a06a89f0f..7f70458dee 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
@@ -1,3 +1,3 @@
# Be sure to restart your server when you modify this file.
-Rails.application.config.action_dispatch.cookies_serializer = :json \ No newline at end of file
+Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
index 72aca7e441..dc1899682b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
@@ -2,4 +2,3 @@
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
-# Mime::Type.register_alias "text/html", :iphone
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index f6f529b80a..584f776c01 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -288,6 +288,10 @@ task default: :test
options[:mountable]
end
+ def skip_git?
+ options[:skip_git]
+ end
+
def with_dummy_app?
options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy'
end
@@ -304,6 +308,24 @@ task default: :test
@camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize
end
+ def author
+ default = "TODO: Write your name"
+ if skip_git?
+ @author = default
+ else
+ @author = `git config user.name`.chomp rescue default
+ end
+ end
+
+ def email
+ default = "TODO: Write your email address"
+ if skip_git?
+ @email = default
+ else
+ @email = `git config user.email`.chomp rescue default
+ end
+ end
+
def valid_const?
if original_name =~ /[^0-9a-zA-Z_]+/
raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters."
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
index 5fdf0e1554..919c349470 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
@@ -7,8 +7,8 @@ require "<%= name %>/version"
Gem::Specification.new do |s|
s.name = "<%= name %>"
s.version = <%= camelized %>::VERSION
- s.authors = ["TODO: Your name"]
- s.email = ["TODO: Your email"]
+ s.authors = ["<%= author %>"]
+ s.email = ["<%= email %>"]
s.homepage = "TODO"
s.summary = "TODO: Summary of <%= camelized %>."
s.description = "TODO: Description of <%= camelized %>."
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index f0a832f783..796587f316 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -1,7 +1,7 @@
-source "https://rubygems.org"
+source 'https://rubygems.org'
<% if options[:skip_gemspec] -%>
-<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>"
+<%= '# ' if options.dev? || options.edge? -%>gem 'rails', '~> <%= Rails::VERSION::STRING %>'
<% else -%>
# Declare your gem's dependencies in <%= name %>.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
@@ -11,7 +11,7 @@ gemspec
<% if options[:skip_gemspec] -%>
group :development do
- gem "<%= gem_for_database %>"
+ gem '<%= gem_for_database %>'
end
<% else -%>
# Declare any dependencies that are still in development here instead of in
@@ -38,6 +38,10 @@ end
<% end -%>
<% unless defined?(JRUBY_VERSION) -%>
-# To use debugger
-# gem 'debugger'
+# To use a debugger
+ <%- if RUBY_VERSION < '2.0.0' -%>
+# gem 'debugger', group: [:development, :test]
+ <%- else -%>
+# gem 'byebug', group: [:development, :test]
+ <%- end -%>
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE
index d7a9109894..ff2fb3ba4e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE
+++ b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright <%= Date.today.year %> YOURNAME
+Copyright <%= Date.today.year %> <%= author %>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 0e69aa101f..2c3b04043f 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -31,7 +31,7 @@ class <%= controller_class_name %>Controller < ApplicationController
if @<%= orm_instance.save %>
redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
else
- render action: 'new'
+ render :new
end
end
@@ -40,7 +40,7 @@ class <%= controller_class_name %>Controller < ApplicationController
if @<%= orm_instance.update("#{singular_table_name}_params") %>
redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
else
- render action: 'edit'
+ render :edit
end
end
diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb
index d1ee96f7fd..886f0e52e1 100644
--- a/railties/lib/rails/rack.rb
+++ b/railties/lib/rails/rack.rb
@@ -1,6 +1,6 @@
module Rails
module Rack
- autoload :Debugger, "rails/rack/debugger"
+ autoload :Debugger, "rails/rack/debugger" if RUBY_VERSION < '2.0.0'
autoload :Logger, "rails/rack/logger"
autoload :LogTailer, "rails/rack/log_tailer"
end
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 3c8f8c6b87..a1c805f8aa 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -55,7 +55,7 @@ namespace :rails do
# desc "Update config/boot.rb from your current rails install"
task :configs do
invoke_from_app_generator :create_boot_file
- invoke_from_app_generator :create_config_files
+ invoke_from_app_generator :update_config_files
end
# desc "Adds new executables to the application bin/ directory"
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 2e6356343d..c837fadb40 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -4,6 +4,7 @@ abort("Abort testing: Your Rails environment is running in production mode!") if
require 'active_support/testing/autorun'
require 'active_support/test_case'
+require 'action_controller'
require 'action_controller/test_case'
require 'action_dispatch/testing/integration'
require 'rails/generators/test_case'
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 6cc17ad176..8f091cfdbf 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -42,7 +42,7 @@ module ApplicationTests
test "assets routes have higher priority" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/javascripts/demo.js.erb", "//= depend_on_asset 'rails.png'\na = <%= image_path('rails.png').inspect %>;"
+ app_file "app/assets/javascripts/demo.js.erb", "a = <%= image_path('rails.png').inspect %>;"
app_file 'config/routes.rb', <<-RUBY
Rails.application.routes.draw do
@@ -50,6 +50,8 @@ module ApplicationTests
end
RUBY
+ add_to_env_config "development", "config.assets.digest = false"
+
require "#{app_path}/config/environment"
get "/assets/demo.js"
@@ -189,7 +191,6 @@ module ApplicationTests
end
test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do
- add_to_config "config.assets.digest = true"
add_to_config "config.action_controller.perform_caching = false"
ENV["RAILS_ENV"] = "production"
@@ -200,10 +201,8 @@ module ApplicationTests
test "precompile creates a manifest file with all the assets listed" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/stylesheets/application.css.erb", "//= depend_on_asset 'rails.png'\n <%= asset_path('rails.png') %>"
+ app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
app_file "app/assets/javascripts/application.js", "alert();"
- # digest is default in false, we must enable it for test environment
- add_to_config "config.assets.digest = true"
precompile!
manifest = Dir["#{app_path}/public/assets/manifest-*.json"].first
@@ -215,8 +214,6 @@ module ApplicationTests
test "the manifest file should be saved by default in the same assets folder" do
app_file "app/assets/javascripts/application.js", "alert();"
- # digest is default in false, we must enable it for test environment
- add_to_config "config.assets.digest = true"
add_to_config "config.assets.prefix = '/x'"
precompile!
@@ -249,7 +246,6 @@ module ApplicationTests
test "precompile properly refers files referenced with asset_path and runs in the provided RAILS_ENV" do
app_file "app/assets/images/rails.png", "notactuallyapng"
app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
- # digest is default in false, we must enable it for test environment
add_to_env_config "test", "config.assets.digest = true"
precompile!('RAILS_ENV=test')
@@ -261,7 +257,7 @@ module ApplicationTests
test "precompile shouldn't use the digests present in manifest.json" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/stylesheets/application.css.erb", "//= depend_on rails.png\np { url: <%= asset_path('rails.png') %> }"
+ app_file "app/assets/stylesheets/application.css.erb", "p { url: <%= asset_path('rails.png') %> }"
ENV["RAILS_ENV"] = "production"
precompile!
@@ -280,13 +276,10 @@ module ApplicationTests
test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do
app_file "app/assets/images/rails.png", "notactuallyapng"
- app_file "app/assets/stylesheets/application.css.erb", "//= depend_on_asset 'rails.png'\n<%= asset_path('rails.png') %>"
- add_to_config "config.assets.compile = true"
- add_to_config "config.assets.digest = true"
-
- ENV["RAILS_ENV"] = nil
+ app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>"
- precompile!('RAILS_GROUPS=assets')
+ ENV["RAILS_ENV"] = "production"
+ precompile!
file = Dir["#{app_path}/public/assets/application-*.css"].first
assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file))
@@ -342,6 +335,8 @@ module ApplicationTests
end
RUBY
+ add_to_env_config "development", "config.assets.digest = false"
+
require "#{app_path}/config/environment"
class ::OmgController < ActionController::Base
@@ -366,6 +361,8 @@ module ApplicationTests
app_file "app/assets/javascripts/demo.js", "alert();"
+ add_to_env_config "development", "config.assets.digest = false"
+
require "#{app_path}/config/environment"
get "/assets/demo.js"
@@ -395,7 +392,6 @@ module ApplicationTests
app_file "app/assets/javascripts/application.js", "//= require_tree ."
app_file "app/assets/javascripts/xmlhr.js.erb", "<%= Post.name %>"
- add_to_config "config.assets.digest = false"
precompile!
assert_equal "Post;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first)
end
@@ -415,7 +411,6 @@ module ApplicationTests
test "digested assets are not mistakenly removed" do
app_file "app/assets/application.js", "alert();"
add_to_config "config.assets.compile = true"
- add_to_config "config.assets.digest = true"
precompile!
@@ -438,6 +433,7 @@ module ApplicationTests
test "asset urls should use the request's protocol by default" do
app_with_assets_in_view
add_to_config "config.asset_host = 'example.com'"
+ add_to_env_config "development", "config.assets.digest = false"
require "#{app_path}/config/environment"
class ::PostsController < ActionController::Base; end
@@ -449,9 +445,10 @@ module ApplicationTests
test "asset urls should be protocol-relative if no request is in scope" do
app_file "app/assets/images/rails.png", "notreallyapng"
- app_file "app/assets/javascripts/image_loader.js.erb", "//= depend_on_asset 'rails.png'\n\nvar src='<%= image_path('rails.png') %>';"
- add_to_config "config.assets.precompile = %w{image_loader.js}"
+ app_file "app/assets/javascripts/image_loader.js.erb", "var src='<%= image_path('rails.png') %>';"
+ add_to_config "config.assets.precompile = %w{rails.png image_loader.js}"
add_to_config "config.asset_host = 'example.com'"
+ add_to_env_config "development", "config.assets.digest = false"
precompile!
assert_match "src='//example.com/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first)
@@ -460,9 +457,9 @@ module ApplicationTests
test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do
ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri"
app_file "app/assets/images/rails.png", "notreallyapng"
-
- app_file "app/assets/javascripts/app.js.erb", "//= depend_on_asset 'rails.png'\n\nvar src='<%= image_path('rails.png') %>';"
- add_to_config "config.assets.precompile = %w{app.js}"
+ app_file "app/assets/javascripts/app.js.erb", "var src='<%= image_path('rails.png') %>';"
+ add_to_config "config.assets.precompile = %w{rails.png app.js}"
+ add_to_env_config "development", "config.assets.digest = false"
precompile!
assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first)
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index b11fd55170..e95c3fa20d 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -360,7 +360,7 @@ module ApplicationTests
test "default method for update can be changed" do
app_file 'app/models/post.rb', <<-RUBY
class Post
- extend ActiveModel::Naming
+ include ActiveModel::Model
def to_key; [1]; end
def persisted?; true; end
end
@@ -489,7 +489,7 @@ module ApplicationTests
test "valid timezone is setup correctly" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.time_zone = "Wellington"
+ config.time_zone = "Wellington"
RUBY
require "#{app_path}/config/environment"
@@ -500,7 +500,7 @@ module ApplicationTests
test "raises when an invalid timezone is defined in the config" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.time_zone = "That big hill over yonder hill"
+ config.time_zone = "That big hill over yonder hill"
RUBY
assert_raise(ArgumentError) do
@@ -511,7 +511,7 @@ module ApplicationTests
test "valid beginning of week is setup correctly" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.beginning_of_week = :wednesday
+ config.beginning_of_week = :wednesday
RUBY
require "#{app_path}/config/environment"
@@ -522,7 +522,7 @@ module ApplicationTests
test "raises when an invalid beginning of week is defined in the config" do
add_to_config <<-RUBY
config.root = "#{app_path}"
- config.beginning_of_week = :invalid
+ config.beginning_of_week = :invalid
RUBY
assert_raise(ArgumentError) do
@@ -803,5 +803,97 @@ module ApplicationTests
assert_not_nil SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/]
end
+
+ test "rake_tasks block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ rake_tasks do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ require 'rake'
+ require 'rake/testtask'
+ require 'rdoc/task'
+
+ Rails.application.load_tasks
+ assert $ran_block
+ end
+
+ test "generators block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ generators do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ Rails.application.load_generators
+ assert $ran_block
+ end
+
+ test "console block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ console do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ Rails.application.load_console
+ assert $ran_block
+ end
+
+ test "runner block works at instance level" do
+ $ran_block = false
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ runner do
+ $ran_block = true
+ end
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ Rails.application.load_runner
+ assert $ran_block
+ end
+
+ test "loading the first existing database configuration available" do
+ app_file 'config/environments/development.rb', <<-RUBY
+
+ Rails.application.configure do
+ config.paths.add 'config/database', with: 'config/nonexistant.yml'
+ config.paths['config/database'] << 'config/database.yml'
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ db_config = Rails.application.config.database_configuration
+
+ assert db_config.is_a?(Hash)
+ end
end
end
diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb
index 5bfea599e0..f8d8a673ae 100644
--- a/railties/test/application/multiple_applications_test.rb
+++ b/railties/test/application/multiple_applications_test.rb
@@ -21,6 +21,12 @@ module ApplicationTests
assert_equal Rails.application.config.secret_key_base, clone.config.secret_key_base, "The base secret key on the config should be the same"
end
+ def test_inheriting_multiple_times_from_application
+ new_application_class = Class.new(Rails::Application)
+
+ assert_not_equal Rails.application.object_id, new_application_class.instance.object_id
+ end
+
def test_initialization_of_multiple_copies_of_same_application
application1 = AppTemplate::Application.new
application2 = AppTemplate::Application.new
@@ -116,6 +122,26 @@ module ApplicationTests
assert_equal 3, $run_count, "There should have been three initializers that incremented the count"
end
+ def test_consoles_run_on_different_applications_go_to_the_same_class
+ $run_count = 0
+ AppTemplate::Application.console { $run_count += 1 }
+ AppTemplate::Application.new.console { $run_count += 1 }
+
+ assert_equal 0, $run_count, "Without loading the consoles, the count should be 0"
+ Rails.application.load_console
+ assert_equal 2, $run_count, "There should have been two consoles that increment the count"
+ end
+
+ def test_generators_run_on_different_applications_go_to_the_same_class
+ $run_count = 0
+ AppTemplate::Application.generators { $run_count += 1 }
+ AppTemplate::Application.new.generators { $run_count += 1 }
+
+ assert_equal 0, $run_count, "Without loading the generators, the count should be 0"
+ Rails.application.load_generators
+ assert_equal 2, $run_count, "There should have been two generators that increment the count"
+ end
+
def test_runners_run_on_different_applications_go_to_the_same_class
$run_count = 0
AppTemplate::Application.runner { $run_count += 1 }
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index b7fd5d02c5..a6900a57c4 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -58,7 +58,7 @@ module ApplicationTests
end
test 'migration status when schema migrations table is not present' do
- output = Dir.chdir(app_path){ `rake db:migrate:status` }
+ output = Dir.chdir(app_path){ `rake db:migrate:status 2>&1` }
assert_equal "Schema migrations table does not exist yet.\n", output
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index a34beaedb3..1273f9d4c2 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -19,14 +19,8 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert console.sandbox?
end
- def test_debugger_option
- console = Rails::Console.new(app, parse_arguments(["--debugger"]))
- assert console.debugger?
- end
-
def test_no_options
console = Rails::Console.new(app, parse_arguments([]))
- assert !console.debugger?
assert !console.sandbox?
end
@@ -36,13 +30,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/Loading \w+ environment \(Rails/, output)
end
- def test_start_with_debugger
- rails_console = Rails::Console.new(app, parse_arguments(["--debugger"]))
- rails_console.expects(:require_debugger).returns(nil)
-
- silence_stream(STDOUT) { rails_console.start }
- end
-
def test_start_with_sandbox
app.expects(:sandbox=).with(true)
FakeConsole.expects(:start)
@@ -52,6 +39,25 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/Loading \w+ environment in sandbox \(Rails/, output)
end
+ if RUBY_VERSION < '2.0.0'
+ def test_debugger_option
+ console = Rails::Console.new(app, parse_arguments(["--debugger"]))
+ assert console.debugger?
+ end
+
+ def test_no_options_does_not_set_debugger_flag
+ console = Rails::Console.new(app, parse_arguments([]))
+ assert !console.debugger?
+ end
+
+ def test_start_with_debugger
+ rails_console = Rails::Console.new(app, parse_arguments(["--debugger"]))
+ rails_console.expects(:require_debugger).returns(nil)
+
+ silence_stream(STDOUT) { rails_console.start }
+ end
+ end
+
def test_console_with_environment
start ["-e production"]
assert_match(/\sproduction\s/, output)
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 8e1aeddb2b..1cbbf62459 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -119,7 +119,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true },
destination_root: app_moved_root, shell: @shell
generator.send(:app_const)
- quietly { generator.send(:create_config_files) }
+ quietly { generator.send(:update_config_files) }
assert_file "myapp_moved/config/environment.rb", /Rails\.application\.initialize!/
assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/
end
@@ -134,10 +134,46 @@ class AppGeneratorTest < Rails::Generators::TestCase
generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
generator.send(:app_const)
- quietly { generator.send(:create_config_files) }
+ quietly { generator.send(:update_config_files) }
assert_file "myapp/config/initializers/session_store.rb", /_myapp_session/
end
+ def test_new_application_use_json_serialzier
+ run_generator
+
+ assert_file("config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
+ end
+
+ def test_rails_update_keep_the_cookie_serializer_if_it_is_already_configured
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :json/)
+ end
+
+ def test_rails_update_set_the_cookie_serializer_to_marchal_if_it_is_not_already_configured
+ app_root = File.join(destination_root, 'myapp')
+ run_generator [app_root]
+
+ FileUtils.rm("#{app_root}/config/initializers/cookies_serializer.rb")
+
+ Rails.application.config.root = app_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell
+ generator.send(:app_const)
+ quietly { generator.send(:update_config_files) }
+ assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/)
+ end
+
def test_application_names_are_not_singularized
run_generator [File.join(destination_root, "hats")]
assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/
@@ -237,6 +273,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
+ assert_no_file "config/initializers/assets.rb"
assert_file "config/application.rb" do |content|
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
end
@@ -252,7 +289,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/config\.assets\.digest = true/, content)
assert_no_match(/config\.assets\.js_compressor = :uglifier/, content)
assert_no_match(/config\.assets\.css_compressor = :sass/, content)
- assert_no_match(/config\.assets\.version = '1\.0'/, content)
end
end
@@ -305,14 +341,17 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /gem 'jbuilder'/
end
- def test_inclusion_of_debugger
+ def test_inclusion_of_a_debugger
run_generator
if defined?(JRUBY_VERSION)
assert_file "Gemfile" do |content|
+ assert_no_match(/byebug/, content)
assert_no_match(/debugger/, content)
end
- else
+ elsif RUBY_VERSION < '2.0.0'
assert_file "Gemfile", /# gem 'debugger'/
+ else
+ assert_file "Gemfile", /# gem 'byebug'/
end
end
diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb
index a94350cbd7..31e07bc8da 100644
--- a/railties/test/generators/argv_scrubber_test.rb
+++ b/railties/test/generators/argv_scrubber_test.rb
@@ -16,7 +16,7 @@ module Rails
output = nil
exit_code = nil
scrubber.extend(Module.new {
- define_method(:puts) { |str| output = str }
+ define_method(:puts) { |string| output = string }
define_method(:exit) { |code| exit_code = code }
})
scrubber.prepare!
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 7a2701f813..7180efee41 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -64,14 +64,17 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/
end
- def test_inclusion_of_debugger
+ def test_inclusion_of_a_debugger
run_generator [destination_root, '--full']
if defined?(JRUBY_VERSION)
assert_file "Gemfile" do |content|
+ assert_no_match(/byebug/, content)
assert_no_match(/debugger/, content)
end
- else
+ elsif RUBY_VERSION < '2.0.0'
assert_file "Gemfile", /# gem 'debugger'/
+ else
+ assert_file "Gemfile", /# gem 'byebug'/
end
end
@@ -309,7 +312,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem "rails", "~> #{Rails.version}"/, contents)
+ assert_match(/gem 'rails', '~> #{Rails.version}'/, contents)
assert_match_sqlite3(contents)
assert_no_match(/# gem "jquery-rails"/, contents)
end
@@ -320,7 +323,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
assert_file "Gemfile" do |contents|
assert_no_match('gemspec', contents)
- assert_match(/gem "rails", "~> #{Rails.version}"/, contents)
+ assert_match(/gem 'rails', '~> #{Rails.version}'/, contents)
assert_match_sqlite3(contents)
end
end
@@ -368,6 +371,40 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_git_name_and_email_in_gemspec_file
+ name = `git config user.name`.chomp rescue "TODO: Write your name"
+ email = `git config user.email`.chomp rescue "TODO: Write your email address"
+
+ run_generator [destination_root]
+ assert_file "bukkits.gemspec" do |contents|
+ assert_match(/#{Regexp.escape(name)}/, contents)
+ assert_match(/#{Regexp.escape(email)}/, contents)
+ end
+ end
+
+ def test_git_name_in_license_file
+ name = `git config user.name`.chomp rescue "TODO: Write your name"
+
+ run_generator [destination_root]
+ assert_file "MIT-LICENSE" do |contents|
+ assert_match(/#{Regexp.escape(name)}/, contents)
+ end
+ end
+
+ def test_no_details_from_git_when_skip_git
+ name = "TODO: Write your name"
+ email = "TODO: Write your email address"
+
+ run_generator [destination_root, '--skip-git']
+ assert_file "MIT-LICENSE" do |contents|
+ assert_match(/#{Regexp.escape(name)}/, contents)
+ end
+ assert_file "bukkits.gemspec" do |contents|
+ assert_match(/#{Regexp.escape(name)}/, contents)
+ assert_match(/#{Regexp.escape(email)}/, contents)
+ end
+ end
+
protected
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
@@ -379,9 +416,9 @@ protected
def assert_match_sqlite3(contents)
unless defined?(JRUBY_VERSION)
- assert_match(/group :development do\n gem "sqlite3"\nend/, contents)
+ assert_match(/group :development do\n gem 'sqlite3'\nend/, contents)
else
- assert_match(/group :development do\n gem "activerecord-jdbcsqlite3-adapter"\nend/, contents)
+ assert_match(/group :development do\n gem 'activerecord-jdbcsqlite3-adapter'\nend/, contents)
end
end
end
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 26e56a162c..3c1123b53d 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -160,13 +160,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
Unknown::Generators.send :remove_const, :ActiveModel
end
- def test_new_hash_style
- run_generator
- assert_file "app/controllers/users_controller.rb" do |content|
- assert_match(/render action: 'new'/, content)
- end
- end
-
def test_model_name_option
run_generator ["Admin::User", "--model-name=User"]
assert_file "app/controllers/admin/users_controller.rb" do |content|
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index c4b18e9ea5..6240dc04ec 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -34,6 +34,7 @@ module RailtiesTest
test "serving sprocket's assets" do
@plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();"
+ add_to_env_config "development", "config.assets.digest = false"
boot_rails
require 'rack/test'
@@ -592,11 +593,15 @@ YAML
@plugin.write "app/models/bukkits/post.rb", <<-RUBY
module Bukkits
class Post
- extend ActiveModel::Naming
+ include ActiveModel::Model
def to_param
"1"
end
+
+ def persisted?
+ true
+ end
end
end
RUBY
@@ -704,8 +709,7 @@ YAML
@plugin.write "app/models/bukkits/post.rb", <<-RUBY
module Bukkits
class Post
- extend ActiveModel::Naming
- include ActiveModel::Conversion
+ include ActiveModel::Model
attr_accessor :title
def to_param
@@ -1077,6 +1081,7 @@ YAML
RUBY
add_to_config("config.railties_order = [:all, :main_app, Blog::Engine]")
+ add_to_env_config "development", "config.assets.digest = false"
boot_rails
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 0ef2ff2e2e..fb2071c7c3 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -88,18 +88,14 @@ module ApplicationTests
@plugin.write "app/models/blog/post.rb", <<-RUBY
module Blog
class Post
- extend ActiveModel::Naming
+ include ActiveModel::Model
def id
44
end
- def to_param
- id.to_s
- end
-
- def new_record?
- false
+ def persisted?
+ true
end
end
end
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index 4cbd4822be..a458240d2f 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -72,6 +72,14 @@ module RailtiesTest
assert $to_prepare
end
+ test "railtie have access to application in before_configuration callbacks" do
+ $after_initialize = false
+ class Foo < Rails::Railtie ; config.before_configuration { $before_configuration = Rails.root.to_path } ; end
+ assert_not $before_configuration
+ require "#{app_path}/config/environment"
+ assert_equal app_path, $before_configuration
+ end
+
test "railtie can add after_initialize callbacks" do
$after_initialize = false
class Foo < Rails::Railtie ; config.after_initialize { $after_initialize = true } ; end