aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml7
-rw-r--r--Gemfile11
-rw-r--r--README.md2
-rw-r--r--actionmailer/CHANGELOG.md7
-rw-r--r--actionmailer/README.rdoc6
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer/base.rb19
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb1
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb8
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb3
-rw-r--r--actionmailer/test/abstract_unit.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.rb27
-rw-r--r--actionmailer/test/log_subscriber_test.rb8
-rw-r--r--actionmailer/test/mail_layout_test.rb10
-rw-r--r--actionpack/CHANGELOG.md119
-rw-r--r--actionpack/README.rdoc7
-rw-r--r--actionpack/RUNNING_UNIT_TESTS.rdoc2
-rw-r--r--actionpack/lib/abstract_controller/base.rb38
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb79
-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.rb19
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb24
-rw-r--r--actionpack/lib/action_controller/metal/live.rb51
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb14
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb22
-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/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb15
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb10
-rw-r--r--actionpack/lib/action_controller/test_case.rb42
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb56
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb63
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb164
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb25
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb4
-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.rb31
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb25
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb91
-rw-r--r--actionpack/lib/action_dispatch/journey/router/strexp.rb15
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb68
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb160
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/index.html.erb4
-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.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/endpoint.rb10
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb15
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb492
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb241
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb22
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb165
-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/integration.rb21
-rw-r--r--actionpack/test/abstract/collector_test.rb16
-rw-r--r--actionpack/test/abstract_unit.rb5
-rw-r--r--actionpack/test/controller/assert_select_test.rb5
-rw-r--r--actionpack/test/controller/caching_test.rb16
-rw-r--r--actionpack/test/controller/content_type_test.rb32
-rw-r--r--actionpack/test/controller/filters_test.rb380
-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/http_token_authentication_test.rb29
-rw-r--r--actionpack/test/controller/integration_test.rb37
-rw-r--r--actionpack/test/controller/live_stream_test.rb105
-rw-r--r--actionpack/test/controller/localized_templates_test.rb18
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb2
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb2
-rw-r--r--actionpack/test/controller/mime/respond_with_test.rb35
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb17
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb19
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb26
-rw-r--r--actionpack/test/controller/render_other_test.rb11
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb38
-rw-r--r--actionpack/test/controller/required_params_test.rb18
-rw-r--r--actionpack/test/controller/routing_test.rb45
-rw-r--r--actionpack/test/controller/send_file_test.rb5
-rw-r--r--actionpack/test/controller/test_case_test.rb12
-rw-r--r--actionpack/test/controller/url_for_test.rb34
-rw-r--r--actionpack/test/dispatch/cookies_test.rb117
-rw-r--r--actionpack/test/dispatch/header_test.rb2
-rw-r--r--actionpack/test/dispatch/mapper_test.rb6
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb2
-rw-r--r--actionpack/test/dispatch/mount_test.rb7
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb3
-rw-r--r--actionpack/test/dispatch/reloader_test.rb5
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb3
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb5
-rw-r--r--actionpack/test/dispatch/request_test.rb8
-rw-r--r--actionpack/test/dispatch/response_test.rb6
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb4
-rw-r--r--actionpack/test/dispatch/routing_test.rb219
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb18
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb30
-rw-r--r--actionpack/test/journey/path/pattern_test.rb46
-rw-r--r--actionpack/test/journey/route_test.rb22
-rw-r--r--actionpack/test/journey/router/strexp_test.rb32
-rw-r--r--actionpack/test/journey/router/utils_test.rb8
-rw-r--r--actionpack/test/journey/router_test.rb193
-rw-r--r--actionpack/test/journey/routes_test.rb10
-rw-r--r--actionview/CHANGELOG.md79
-rw-r--r--actionview/README.rdoc7
-rw-r--r--actionview/RUNNING_UNIT_TESTS.rdoc2
-rw-r--r--actionview/lib/action_view/base.rb3
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb49
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb49
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb79
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb18
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb9
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb1
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb3
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb8
-rw-r--r--actionview/lib/action_view/lookup_context.rb9
-rw-r--r--actionview/lib/action_view/rendering.rb4
-rw-r--r--actionview/lib/action_view/routing_url_for.rb16
-rw-r--r--actionview/lib/action_view/tasks/dependencies.rake16
-rw-r--r--actionview/lib/action_view/template/handlers.rb9
-rw-r--r--actionview/lib/action_view/template/resolver.rb30
-rw-r--r--actionview/lib/action_view/view_paths.rb41
-rw-r--r--actionview/test/abstract_unit.rb1
-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/layout_test.rb33
-rw-r--r--actionview/test/actionpack/controller/render_test.rb6
-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/capture_helper_test.rb23
-rw-r--r--actionview/test/template/dependency_tracker_test.rb1
-rw-r--r--actionview/test/template/digestor_test.rb1
-rw-r--r--actionview/test/template/form_helper_test.rb22
-rw-r--r--actionview/test/template/javascript_helper_test.rb6
-rw-r--r--actionview/test/template/number_helper_test.rb3
-rw-r--r--actionview/test/template/output_buffer_test.rb59
-rw-r--r--actionview/test/template/output_safety_helper_test.rb9
-rw-r--r--actionview/test/template/render_test.rb30
-rw-r--r--actionview/test/template/sanitize_helper_test.rb2
-rw-r--r--actionview/test/template/tag_helper_test.rb24
-rw-r--r--actionview/test/template/test_test.rb18
-rw-r--r--actionview/test/template/translation_helper_test.rb12
-rw-r--r--activemodel/CHANGELOG.md20
-rw-r--r--activemodel/README.rdoc9
-rw-r--r--activemodel/lib/active_model/conversion.rb17
-rw-r--r--activemodel/lib/active_model/locale/en.yml12
-rw-r--r--activemodel/lib/active_model/model.rb6
-rw-r--r--activemodel/lib/active_model/naming.rb7
-rw-r--r--activemodel/lib/active_model/secure_password.rb31
-rw-r--r--activemodel/lib/active_model/validator.rb16
-rw-r--r--activemodel/test/cases/conversion_test.rb4
-rw-r--r--activemodel/test/cases/secure_password_test.rb56
-rw-r--r--activemodel/test/cases/validations/absence_validation_test.rb7
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb18
-rw-r--r--activemodel/test/cases/validations_test.rb21
-rw-r--r--activerecord/CHANGELOG.md436
-rw-r--r--activerecord/README.rdoc23
-rw-r--r--activerecord/Rakefile191
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/aggregations.rb20
-rw-r--r--activerecord/lib/active_record/associations.rb33
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb12
-rw-r--r--activerecord/lib/active_record/associations/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb64
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb14
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb34
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb29
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb55
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb15
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb19
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb17
-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.rb27
-rw-r--r--activerecord/lib/active_record/attribute.rb56
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb18
-rw-r--r--activerecord/lib/active_record/attribute_decorators.rb34
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb115
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb122
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb72
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb150
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb47
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb40
-rw-r--r--activerecord/lib/active_record/attributes.rb122
-rw-r--r--activerecord/lib/active_record/autosave_association.rb63
-rw-r--r--activerecord/lib/active_record/base.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb122
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb40
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb128
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb186
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb268
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb192
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb81
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb150
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb466
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb46
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb60
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb85
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb62
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb150
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb53
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/utils.rb66
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb409
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb58
-rw-r--r--activerecord/lib/active_record/core.rb136
-rw-r--r--activerecord/lib/active_record/counter_cache.rb20
-rw-r--r--activerecord/lib/active_record/enum.rb13
-rw-r--r--activerecord/lib/active_record/errors.rb43
-rw-r--r--activerecord/lib/active_record/fixtures.rb37
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb19
-rw-r--r--activerecord/lib/active_record/model_schema.rb74
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb6
-rw-r--r--activerecord/lib/active_record/null_relation.rb20
-rw-r--r--activerecord/lib/active_record/persistence.rb23
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake14
-rw-r--r--activerecord/lib/active_record/reflection.rb134
-rw-r--r--activerecord/lib/active_record/relation.rb50
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb30
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb25
-rw-r--r--activerecord/lib/active_record/relation/merger.rb12
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb19
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb27
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb84
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb3
-rw-r--r--activerecord/lib/active_record/result.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb4
-rw-r--r--activerecord/lib/active_record/schema_migration.rb7
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb10
-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.rb5
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
-rw-r--r--activerecord/lib/active_record/transactions.rb28
-rw-r--r--activerecord/lib/active_record/type.rb20
-rw-r--r--activerecord/lib/active_record/type/binary.rb40
-rw-r--r--activerecord/lib/active_record/type/boolean.rb19
-rw-r--r--activerecord/lib/active_record/type/date.rb46
-rw-r--r--activerecord/lib/active_record/type/date_time.rb33
-rw-r--r--activerecord/lib/active_record/type/decimal.rb25
-rw-r--r--activerecord/lib/active_record/type/decimal_without_scale.rb11
-rw-r--r--activerecord/lib/active_record/type/float.rb19
-rw-r--r--activerecord/lib/active_record/type/hash_lookup_type_map.rb19
-rw-r--r--activerecord/lib/active_record/type/integer.rb23
-rw-r--r--activerecord/lib/active_record/type/mutable.rb16
-rw-r--r--activerecord/lib/active_record/type/numeric.rb42
-rw-r--r--activerecord/lib/active_record/type/serialized.rb51
-rw-r--r--activerecord/lib/active_record/type/string.rb23
-rw-r--r--activerecord/lib/active_record/type/text.rb11
-rw-r--r--activerecord/lib/active_record/type/time.rb26
-rw-r--r--activerecord/lib/active_record/type/time_value.rb38
-rw-r--r--activerecord/lib/active_record/type/type_map.rb48
-rw-r--r--activerecord/lib/active_record/type/value.rb78
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb11
-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/consistency_test.rb48
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb2
-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.rb96
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb80
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb98
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb239
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb11
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb63
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb64
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb84
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb49
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb74
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb74
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb38
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb39
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb75
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb47
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb5
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb1
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb20
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb20
-rw-r--r--activerecord/test/cases/ar_schema_test.rb24
-rw-r--r--activerecord/test/cases/associations/association_scope_test.rb5
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb20
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_test.rb71
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb57
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb42
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb36
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb10
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb13
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb114
-rw-r--r--activerecord/test/cases/attribute_methods/serialization_test.rb29
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb64
-rw-r--r--activerecord/test/cases/attribute_test.rb103
-rw-r--r--activerecord/test/cases/attributes_test.rb111
-rw-r--r--activerecord/test/cases/autosave_association_test.rb13
-rw-r--r--activerecord/test/cases/base_test.rb69
-rw-r--r--activerecord/test/cases/binary_test.rb6
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb8
-rw-r--r--activerecord/test/cases/calculations_test.rb11
-rw-r--r--activerecord/test/cases/column_definition_test.rb58
-rw-r--r--activerecord/test/cases/column_test.rb123
-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.rb193
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb204
-rw-r--r--activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb61
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb101
-rw-r--r--activerecord/test/cases/core_test.rb68
-rw-r--r--activerecord/test/cases/counter_cache_test.rb16
-rw-r--r--activerecord/test/cases/defaults_test.rb7
-rw-r--r--activerecord/test/cases/dirty_test.rb37
-rw-r--r--activerecord/test/cases/dup_test.rb9
-rw-r--r--activerecord/test/cases/explain_test.rb8
-rw-r--r--activerecord/test/cases/finder_test.rb11
-rw-r--r--activerecord/test/cases/fixtures_test.rb6
-rw-r--r--activerecord/test/cases/helper.rb1
-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.rb8
-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/logger_test.rb2
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb49
-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.rb70
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb61
-rw-r--r--activerecord/test/cases/quoting_test.rb28
-rw-r--r--activerecord/test/cases/reflection_test.rb27
-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.rb7
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb87
-rw-r--r--activerecord/test/cases/relations_test.rb92
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb44
-rw-r--r--activerecord/test/cases/serialization_test.rb27
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb118
-rw-r--r--activerecord/test/cases/statement_cache_test.rb64
-rw-r--r--activerecord/test/cases/store_test.rb31
-rw-r--r--activerecord/test/cases/test_case.rb8
-rw-r--r--activerecord/test/cases/timestamp_test.rb52
-rw-r--r--activerecord/test/cases/transactions_test.rb13
-rw-r--r--activerecord/test/cases/type/type_map_test.rb130
-rw-r--r--activerecord/test/cases/types_test.rb159
-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.rb17
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb51
-rw-r--r--activerecord/test/config.example.yml30
-rw-r--r--activerecord/test/fixtures/books.yml2
-rw-r--r--activerecord/test/fixtures/topics.yml10
-rw-r--r--activerecord/test/models/author.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/face.rb1
-rw-r--r--activerecord/test/models/man.rb1
-rw-r--r--activerecord/test/models/owner.rb12
-rw-r--r--activerecord/test/models/pirate.rb6
-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/schema/postgresql_specific_schema.rb17
-rw-r--r--activerecord/test/schema/schema.rb33
-rw-r--r--activerecord/test/support/schema_dumping_helper.rb11
-rw-r--r--activesupport/CHANGELOG.md123
-rw-r--r--activesupport/README.rdoc7
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb6
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb5
-rw-r--r--activesupport/lib/active_support/callbacks.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb12
-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/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb58
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb2
-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/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb32
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb9
-rw-r--r--activesupport/lib/active_support/duration.rb3
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb11
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb48
-rw-r--r--activesupport/lib/active_support/notifications.rb9
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb17
-rw-r--r--activesupport/lib/active_support/number_helper.rb15
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb4
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb6
-rw-r--r--activesupport/lib/active_support/subscriber.rb11
-rw-r--r--activesupport/lib/active_support/test_case.rb2
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb2
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb24
-rw-r--r--activesupport/lib/active_support/time.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb17
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb146
-rw-r--r--activesupport/test/caching_test.rb5
-rw-r--r--activesupport/test/constantize_test_cases.rb11
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb41
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb56
-rw-r--r--activesupport/test/core_ext/module_test.rb2
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb44
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb (renamed from activesupport/test/core_ext/blank_test.rb)0
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb4
-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/string_ext_test.rb5
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb15
-rw-r--r--activesupport/test/i18n_test.rb1
-rw-r--r--activesupport/test/inflector_test.rb15
-rw-r--r--activesupport/test/inflector_test_cases.rb8
-rw-r--r--activesupport/test/multibyte_conformance_test.rb4
-rw-r--r--activesupport/test/number_helper_i18n_test.rb16
-rw-r--r--activesupport/test/number_helper_test.rb4
-rw-r--r--activesupport/test/subscriber_test.rb26
-rw-r--r--activesupport/test/test_test.rb20
-rw-r--r--activesupport/test/time_zone_test.rb9
-rwxr-xr-xci/travis.rb2
-rw-r--r--guides/CHANGELOG.md18
-rw-r--r--guides/assets/stylesheets/main.css5
-rw-r--r--guides/bug_report_templates/active_record_master.rb1
-rw-r--r--guides/code/getting_started/.gitignore16
-rw-r--r--guides/code/getting_started/Gemfile40
-rw-r--r--guides/code/getting_started/Gemfile.lock126
-rw-r--r--guides/code/getting_started/README.rdoc28
-rw-r--r--guides/code/getting_started/Rakefile6
-rw-r--r--guides/code/getting_started/app/assets/javascripts/application.js15
-rw-r--r--guides/code/getting_started/app/assets/javascripts/comments.js.coffee3
-rw-r--r--guides/code/getting_started/app/assets/javascripts/posts.js.coffee3
-rw-r--r--guides/code/getting_started/app/assets/javascripts/welcome.js.coffee3
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/application.css13
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/comments.css.scss3
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/posts.css.scss3
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/welcome.css.scss3
-rw-r--r--guides/code/getting_started/app/controllers/application_controller.rb5
-rw-r--r--guides/code/getting_started/app/controllers/comments_controller.rb23
-rw-r--r--guides/code/getting_started/app/controllers/concerns/.keep0
-rw-r--r--guides/code/getting_started/app/controllers/posts_controller.rb53
-rw-r--r--guides/code/getting_started/app/controllers/welcome_controller.rb4
-rw-r--r--guides/code/getting_started/app/helpers/application_helper.rb2
-rw-r--r--guides/code/getting_started/app/helpers/comments_helper.rb2
-rw-r--r--guides/code/getting_started/app/helpers/posts_helper.rb2
-rw-r--r--guides/code/getting_started/app/helpers/welcome_helper.rb2
-rw-r--r--guides/code/getting_started/app/mailers/.keep0
-rw-r--r--guides/code/getting_started/app/models/.keep0
-rw-r--r--guides/code/getting_started/app/models/comment.rb3
-rw-r--r--guides/code/getting_started/app/models/concerns/.keep0
-rw-r--r--guides/code/getting_started/app/models/post.rb7
-rw-r--r--guides/code/getting_started/app/views/comments/_comment.html.erb15
-rw-r--r--guides/code/getting_started/app/views/comments/_form.html.erb13
-rw-r--r--guides/code/getting_started/app/views/layouts/application.html.erb14
-rw-r--r--guides/code/getting_started/app/views/posts/_form.html.erb27
-rw-r--r--guides/code/getting_started/app/views/posts/edit.html.erb5
-rw-r--r--guides/code/getting_started/app/views/posts/index.html.erb21
-rw-r--r--guides/code/getting_started/app/views/posts/new.html.erb5
-rw-r--r--guides/code/getting_started/app/views/posts/show.html.erb18
-rw-r--r--guides/code/getting_started/app/views/welcome/index.html.erb4
-rwxr-xr-xguides/code/getting_started/bin/bundle4
-rwxr-xr-xguides/code/getting_started/bin/rails4
-rwxr-xr-xguides/code/getting_started/bin/rake4
-rw-r--r--guides/code/getting_started/config.ru4
-rw-r--r--guides/code/getting_started/config/application.rb18
-rw-r--r--guides/code/getting_started/config/boot.rb4
-rw-r--r--guides/code/getting_started/config/database.yml25
-rw-r--r--guides/code/getting_started/config/environment.rb5
-rw-r--r--guides/code/getting_started/config/environments/development.rb30
-rw-r--r--guides/code/getting_started/config/environments/production.rb80
-rw-r--r--guides/code/getting_started/config/environments/test.rb36
-rw-r--r--guides/code/getting_started/config/initializers/backtrace_silencers.rb7
-rw-r--r--guides/code/getting_started/config/initializers/filter_parameter_logging.rb4
-rw-r--r--guides/code/getting_started/config/initializers/inflections.rb16
-rw-r--r--guides/code/getting_started/config/initializers/locale.rb9
-rw-r--r--guides/code/getting_started/config/initializers/mime_types.rb5
-rw-r--r--guides/code/getting_started/config/initializers/secret_token.rb12
-rw-r--r--guides/code/getting_started/config/initializers/session_store.rb3
-rw-r--r--guides/code/getting_started/config/initializers/wrap_parameters.rb14
-rw-r--r--guides/code/getting_started/config/locales/en.yml23
-rw-r--r--guides/code/getting_started/config/routes.rb7
-rw-r--r--guides/code/getting_started/db/migrate/20130122042648_create_posts.rb10
-rw-r--r--guides/code/getting_started/db/migrate/20130122045842_create_comments.rb11
-rw-r--r--guides/code/getting_started/db/schema.rb33
-rw-r--r--guides/code/getting_started/db/seeds.rb7
-rw-r--r--guides/code/getting_started/lib/assets/.keep0
-rw-r--r--guides/code/getting_started/lib/tasks/.keep0
-rw-r--r--guides/code/getting_started/log/.keep0
-rw-r--r--guides/code/getting_started/public/404.html60
-rw-r--r--guides/code/getting_started/public/422.html60
-rw-r--r--guides/code/getting_started/public/500.html59
-rw-r--r--guides/code/getting_started/public/favicon.ico0
-rw-r--r--guides/code/getting_started/public/robots.txt5
-rw-r--r--guides/code/getting_started/test/controllers/.keep0
-rw-r--r--guides/code/getting_started/test/controllers/comments_controller_test.rb7
-rw-r--r--guides/code/getting_started/test/controllers/posts_controller_test.rb7
-rw-r--r--guides/code/getting_started/test/controllers/welcome_controller_test.rb9
-rw-r--r--guides/code/getting_started/test/fixtures/.keep0
-rw-r--r--guides/code/getting_started/test/fixtures/comments.yml11
-rw-r--r--guides/code/getting_started/test/fixtures/posts.yml9
-rw-r--r--guides/code/getting_started/test/helpers/.keep0
-rw-r--r--guides/code/getting_started/test/helpers/comments_helper_test.rb4
-rw-r--r--guides/code/getting_started/test/helpers/posts_helper_test.rb4
-rw-r--r--guides/code/getting_started/test/helpers/welcome_helper_test.rb4
-rw-r--r--guides/code/getting_started/test/integration/.keep0
-rw-r--r--guides/code/getting_started/test/mailers/.keep0
-rw-r--r--guides/code/getting_started/test/models/.keep0
-rw-r--r--guides/code/getting_started/test/models/comment_test.rb7
-rw-r--r--guides/code/getting_started/test/models/post_test.rb7
-rw-r--r--guides/code/getting_started/test/test_helper.rb15
-rw-r--r--guides/code/getting_started/vendor/assets/javascripts/.keep0
-rw-r--r--guides/code/getting_started/vendor/assets/stylesheets/.keep0
-rw-r--r--guides/rails_guides.rb4
-rw-r--r--guides/rails_guides/helpers.rb2
-rw-r--r--guides/rails_guides/markdown.rb13
-rw-r--r--guides/source/2_3_release_notes.md6
-rw-r--r--guides/source/3_0_release_notes.md6
-rw-r--r--guides/source/3_1_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md243
-rw-r--r--guides/source/_license.html.erb2
-rw-r--r--guides/source/_welcome.html.erb8
-rw-r--r--guides/source/action_controller_overview.md4
-rw-r--r--guides/source/action_mailer_basics.md37
-rw-r--r--guides/source/action_view_overview.md220
-rw-r--r--guides/source/active_record_basics.md22
-rw-r--r--guides/source/active_record_callbacks.md16
-rw-r--r--guides/source/active_record_migrations.md (renamed from guides/source/migrations.md)89
-rw-r--r--guides/source/active_record_postgresql.md437
-rw-r--r--guides/source/active_record_querying.md250
-rw-r--r--guides/source/active_record_validations.md12
-rw-r--r--guides/source/active_support_core_extensions.md45
-rw-r--r--guides/source/active_support_instrumentation.md5
-rw-r--r--guides/source/api_documentation_guidelines.md91
-rw-r--r--guides/source/asset_pipeline.md69
-rw-r--r--guides/source/association_basics.md40
-rw-r--r--guides/source/caching_with_rails.md4
-rw-r--r--guides/source/command_line.md87
-rw-r--r--guides/source/configuring.md58
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md119
-rw-r--r--guides/source/debugging_rails_applications.md169
-rw-r--r--guides/source/development_dependencies_install.md4
-rw-r--r--guides/source/documents.yaml10
-rw-r--r--guides/source/engines.md320
-rw-r--r--guides/source/form_helpers.md89
-rw-r--r--guides/source/generators.md30
-rw-r--r--guides/source/getting_started.md500
-rw-r--r--guides/source/i18n.md31
-rw-r--r--guides/source/index.html.erb1
-rw-r--r--guides/source/layout.html.erb5
-rw-r--r--guides/source/layouts_and_rendering.md24
-rw-r--r--guides/source/maintenance_policy.md27
-rw-r--r--guides/source/plugins.md14
-rw-r--r--guides/source/rails_application_templates.md4
-rw-r--r--guides/source/rails_on_rack.md6
-rw-r--r--guides/source/routing.md168
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md2
-rw-r--r--guides/source/security.md22
-rw-r--r--guides/source/testing.md191
-rw-r--r--guides/source/upgrading_ruby_on_rails.md33
-rw-r--r--guides/source/working_with_javascript_in_rails.md30
-rw-r--r--guides/w3c_validator.rb2
-rw-r--r--railties/CHANGELOG.md30
-rw-r--r--railties/RDOC_MAIN.rdoc2
-rw-r--r--railties/README.rdoc6
-rw-r--r--railties/lib/rails/application.rb9
-rw-r--r--railties/lib/rails/application/configuration.rb2
-rw-r--r--railties/lib/rails/commands/dbconsole.rb2
-rw-r--r--railties/lib/rails/generators.rb83
-rw-r--r--railties/lib/rails/generators/actions.rb17
-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.erb5
-rw-r--r--railties/lib/rails/generators/named_base.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb15
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup28
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml2
-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.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb3
-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/Gemfile6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile4
-rw-r--r--railties/lib/rails/info_controller.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb6
-rw-r--r--railties/lib/rails/ruby_version_check.rb2
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/tasks/statistics.rake11
-rw-r--r--railties/lib/rails/test_unit/railtie.rb2
-rw-r--r--railties/test/application/assets_test.rb29
-rw-r--r--railties/test/application/configuration_test.rb50
-rw-r--r--railties/test/application/rake/migrations_test.rb2
-rw-r--r--railties/test/application/test_test.rb91
-rw-r--r--railties/test/generators/actions_test.rb22
-rw-r--r--railties/test/generators/app_generator_test.rb41
-rw-r--r--railties/test/generators/argv_scrubber_test.rb4
-rw-r--r--railties/test/generators/generator_test.rb2
-rw-r--r--railties/test/generators/plugin_generator_test.rb42
-rw-r--r--railties/test/generators/shared_generator_tests.rb13
-rw-r--r--railties/test/generators_test.rb12
-rw-r--r--railties/test/rails_info_test.rb10
-rw-r--r--railties/test/railties/engine_test.rb43
-rw-r--r--railties/test/railties/mounted_engine_test.rb10
698 files changed, 14833 insertions, 8946 deletions
diff --git a/.travis.yml b/.travis.yml
index 16086e2663..8eee7b129b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,3 @@
-services: memcache
script: 'ci/travis.rb'
before_install:
- travis_retry gem install bundler
@@ -6,7 +5,8 @@ before_install:
rvm:
- 1.9.3
- 2.0.0
- - 2.1.1
+ - 2.1
+ - ruby-head
- rbx-2
- jruby
env:
@@ -18,8 +18,11 @@ env:
- "GEM=ar:postgresql"
matrix:
allow_failures:
+ - rvm: 1.9.3
+ env: "GEM=ar:mysql"
- rvm: rbx-2
- rvm: jruby
+ - rvm: ruby-head
fast_finish: true
notifications:
email: false
diff --git a/Gemfile b/Gemfile
index ab4cc0ff74..2a695df618 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,7 +11,9 @@ gem 'rack-cache', '~> 1.2'
gem 'jquery-rails', '~> 3.1.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
-gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: '2-1-stable'
+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)
@@ -24,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
@@ -37,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
@@ -84,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 c3577efe53..6a73727eed 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Rails is a web-application framework that includes everything needed to
create database-backed web applications according to the
-[Model-View-Controller (MVC)](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
+[Model-View-Controller (MVC)](http://en.wikipedia.org/wiki/Model-view-controller)
pattern.
Understanding the MVC pattern is key to understanding Rails. MVC divides your
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 6203699405..97ff62fa87 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1 +1,8 @@
+* Allow preview interceptors to be registered through
+ `config.action_mailer.preview_interceptors`.
+
+ See #15739.
+
+ *Yves Senn*
+
Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 67b64fe469..ceca912ada 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -156,7 +156,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 9b25feaf75..01d97b7213 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -22,5 +22,5 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
s.add_dependency 'actionview', version
- s.add_dependency 'mail', '~> 2.5.4'
+ s.add_dependency 'mail', ['~> 2.5', '>= 2.5.4']
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 951a3e5fb5..135cf35ac0 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>,
@@ -301,12 +301,13 @@ module ActionMailer
# end
# end
#
- # Callbacks in ActionMailer are implemented using AbstractController::Callbacks, so you
- # can define and configure callbacks in the same manner that you would use callbacks in
- # classes that inherit from ActionController::Base.
+ # Callbacks in Action Mailer are implemented using
+ # <tt>AbstractController::Callbacks</tt>, so you can define and configure
+ # callbacks in the same manner that you would use callbacks in classes that
+ # inherit from <tt>ActionController::Base</tt>.
#
# Note that unless you have a specific reason to do so, you should prefer using before_action
- # rather than after_action in your ActionMailer classes so that headers are parsed properly.
+ # rather than after_action in your Action Mailer classes so that headers are parsed properly.
#
# = Previewing emails
#
@@ -325,7 +326,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.
@@ -339,7 +340,7 @@ module ActionMailer
# end
# end
#
- # config.action_mailer.register_preview_interceptor :css_inline_styler
+ # config.action_mailer.preview_interceptors :css_inline_styler
#
# Note that interceptors need to be registered both with <tt>register_interceptor</tt>
# and <tt>register_preview_interceptor</tt> if they should operate on both sending and
@@ -368,8 +369,8 @@ module ActionMailer
# This is a symbol and one of <tt>:plain</tt> (will send the password in the clear), <tt>:login</tt> (will
# send password Base64 encoded) or <tt>:cram_md5</tt> (combines a Challenge/Response mechanism to exchange
# information and a cryptographic Message Digest 5 algorithm to hash important information)
- # * <tt>:enable_starttls_auto</tt> - When set to true, detects if STARTTLS is enabled in your SMTP server
- # and starts to use it.
+ # * <tt>:enable_starttls_auto</tt> - Detects if STARTTLS is enabled in your SMTP server and starts
+ # to use it. Defaults to <tt>true</tt>.
# * <tt>:openssl_verify_mode</tt> - When using TLS, you can set how OpenSSL checks the certificate. This is
# really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name
# of an OpenSSL verify constant (<tt>'none'</tt>, <tt>'peer'</tt>, <tt>'client_once'</tt>,
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 8d1e40297b..671551fa53 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -33,6 +33,7 @@ module ActionMailer
include app.routes.mounted_helpers
register_interceptors(options.delete(:interceptors))
+ register_preview_interceptors(options.delete(:preview_interceptors))
register_observers(options.delete(:observers))
options.each { |k,v| send("#{k}=", v) }
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index 207f949fe2..a5442c0316 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -20,6 +20,7 @@ module ActionMailer
class_attribute :_mailer_class
setup :initialize_test_deliveries
setup :set_expected_mail
+ teardown :restore_test_deliveries
end
module ClassMethods
@@ -54,8 +55,15 @@ module ActionMailer
protected
def initialize_test_deliveries
+ @old_delivery_method = ActionMailer::Base.delivery_method
+ @old_perform_deliveries = ActionMailer::Base.perform_deliveries
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
+ end
+
+ def restore_test_deliveries
+ ActionMailer::Base.delivery_method = @old_delivery_method
+ ActionMailer::Base.perform_deliveries = @old_perform_deliveries
ActionMailer::Base.deliveries.clear
end
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 6452bf616c..06da0dd27e 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -1,4 +1,6 @@
module ActionMailer
+ # Provides helper methods for testing Action Mailer, including #assert_emails
+ # and #assert_no_emails
module TestHelper
# Asserts that the number of emails sent matches the given number.
#
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index a98aec913f..06f80a8fdc 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,7 +1,8 @@
require_relative 'gem_version'
module ActionMailer
- # Returns the version of the currently loaded ActionMailer as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded Action Mailer as a
+ # <tt>Gem::Version</tt>.
def self.version
gem_version
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 93d16f491d..4e5a9c3715 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -23,10 +23,6 @@ ActiveSupport::Deprecation.debug = true
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-# Bogus template processors
-ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!".inspect }
-ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup".inspect }
-
FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__))
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
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..d9d588a950 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,23 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes
end
- def setup
- I18n.backend.store_translations('de', email_subject: '[Signed up] Welcome')
+ teardown do
+ I18n.locale = I18n.default_locale
end
- def teardown
- I18n.locale = :en
+ 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 test_send_mail
- get '/test/send_mail'
- assert_equal "Mail sent", @response.body
+ protected
+
+ 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 7e3a426eb2..c38b31903b 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,117 @@
+* Fix env['PATH_INFO'] missing leading slash when a rack app mounted at '/'.
+
+ Fixes #15511.
+
+ *Larry Lv*
+
+* ActionController::Parameters#require now accepts `false` values.
+
+ Fixes #15685.
+
+ *Sergio Romano*
+
+* With authorization header `Authorization: Token token=`, `authenticate` now
+ recognize token as nil, instead of "token".
+
+ Fixes #14846.
+
+ *Larry Lv*
+
+* Ensure the controller is always notified as soon as the client disconnects
+ during live streaming, even when the controller is blocked on a write.
+
+ *Nicholas Jakobsen*, *Matthew Draper*
+
+* Routes specifying 'to:' must be a string that contains a "#" or a rack
+ application. Use of a symbol should be replaced with `action: symbol`.
+ Use of a string without a "#" should be replaced with `controller: string`.
+
+* Fix URL generation with `:trailing_slash` such that it does not add
+ a trailing slash after `.:format`
+
+ *Dan Langevin*
+
+* Build full URI as string when processing path in integration tests for
+ performance reasons.
+
+ *Guo Xiang Tan*
+
+* 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`.
@@ -20,12 +134,12 @@
*Andrew White*, *James Coglan*
-* Append link to bad code to backtrace when exception is SyntaxError.
+* 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.
@@ -50,4 +164,5 @@
*Tony Wooster*
+
Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 2f6575c3b5..02a24a7412 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -48,6 +48,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc
index 2f923136d9..f96a9d9da5 100644
--- a/actionpack/RUNNING_UNIT_TESTS.rdoc
+++ b/actionpack/RUNNING_UNIT_TESTS.rdoc
@@ -4,7 +4,7 @@ The easiest way to run the unit tests is through Rake. The default task runs
the entire test suite for all classes. For more information, check out the
full array of rake tasks with "rake -T".
-Rake can be found at http://rake.rubyforge.org.
+Rake can be found at http://docs.seattlerb.org/rake/.
== Running by hand
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index af5de815bb..acdfb33efa 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 !~ 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..ca5c80cd71 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -42,20 +42,18 @@ module AbstractController
end
end
- # Skip before, after, and around action callbacks matching any of the names
- # Aliased as skip_filter.
+ # Skip before, after, and around action callbacks matching any of the names.
#
# ==== Parameters
# * <tt>names</tt> - A list of valid names that could be used for
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
- # using #skip_filter
+ # using #skip_action_callback
def skip_action_callback(*names)
skip_before_action(*names)
skip_after_action(*names)
skip_around_action(*names)
end
-
alias_method :skip_filter, :skip_action_callback
# Take callback names and an optional callback proc, normalize them,
@@ -85,7 +83,6 @@ module AbstractController
# :call-seq: before_action(names, block)
#
# Append a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as before_filter.
##
# :method: prepend_before_action
@@ -93,7 +90,6 @@ module AbstractController
# :call-seq: prepend_before_action(names, block)
#
# Prepend a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as prepend_before_filter.
##
# :method: skip_before_action
@@ -101,7 +97,6 @@ module AbstractController
# :call-seq: skip_before_action(names)
#
# Skip a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as skip_before_filter.
##
# :method: append_before_action
@@ -109,7 +104,6 @@ module AbstractController
# :call-seq: append_before_action(names, block)
#
# Append a callback before actions. See _insert_callbacks for parameter details.
- # Aliased as append_before_filter.
##
# :method: after_action
@@ -117,7 +111,6 @@ module AbstractController
# :call-seq: after_action(names, block)
#
# Append a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as after_filter.
##
# :method: prepend_after_action
@@ -125,7 +118,6 @@ module AbstractController
# :call-seq: prepend_after_action(names, block)
#
# Prepend a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as prepend_after_filter.
##
# :method: skip_after_action
@@ -133,7 +125,6 @@ module AbstractController
# :call-seq: skip_after_action(names)
#
# Skip a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as skip_after_filter.
##
# :method: append_after_action
@@ -141,7 +132,6 @@ module AbstractController
# :call-seq: append_after_action(names, block)
#
# Append a callback after actions. See _insert_callbacks for parameter details.
- # Aliased as append_after_filter.
##
# :method: around_action
@@ -149,7 +139,6 @@ module AbstractController
# :call-seq: around_action(names, block)
#
# Append a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as around_filter.
##
# :method: prepend_around_action
@@ -157,7 +146,6 @@ module AbstractController
# :call-seq: prepend_around_action(names, block)
#
# Prepend a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as prepend_around_filter.
##
# :method: skip_around_action
@@ -165,7 +153,6 @@ module AbstractController
# :call-seq: skip_around_action(names)
#
# Skip a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as skip_around_filter.
##
# :method: append_around_action
@@ -173,46 +160,36 @@ module AbstractController
# :call-seq: append_around_action(names, block)
#
# Append a callback around actions. See _insert_callbacks for parameter details.
- # Aliased as append_around_filter.
# 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_#{callback}_filter", :"#{callback}_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 0f4cc7a8f5..9a427ebfdb 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -30,10 +30,8 @@ module ActionController
end
end
- def build(action, app=nil, &block)
- app ||= block
+ def build(action, app = Proc.new)
action = action.to_s
- raise "MiddlewareStack#build requires an app" unless app
middlewares.reverse.inject(app) do |a, middleware|
middleware.valid?(action) ? middleware.build(a) : a
@@ -223,14 +221,23 @@ module ActionController
# Makes the controller a Rack endpoint that runs the action in the given
# +env+'s +action_dispatch.request.path_parameters+ key.
def self.call(env)
- action(env['action_dispatch.request.path_parameters'][:action]).call(env)
+ req = ActionDispatch::Request.new env
+ action(req.path_parameters[:action]).call(env)
end
# Returns a Rack endpoint for the given action name.
def self.action(name, klass = ActionDispatch::Request)
- middleware_stack.build(name.to_s) do |env|
- new.dispatch(name, klass.new(env))
+ if middleware_stack.any?
+ middleware_stack.build(name) do |env|
+ new.dispatch(name, klass.new(env))
+ end
+ else
+ lambda { |env| 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..5b52c19802 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)
@@ -109,8 +121,8 @@ module ActionController
def authentication_request(controller, realm)
controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
- controller.response_body = "HTTP Basic: Access denied.\n"
controller.status = 401
+ controller.response_body = "HTTP Basic: Access denied.\n"
end
end
@@ -244,8 +256,8 @@ module ActionController
def authentication_request(controller, realm, message = nil)
message ||= "HTTP Digest: Access denied.\n"
authentication_header(controller, realm)
- controller.response_body = message
controller.status = 401
+ controller.response_body = message
end
def secret_token(request)
@@ -437,7 +449,7 @@ module ActionController
authorization_request = request.authorization.to_s
if authorization_request[TOKEN_REGEX]
params = token_params_from authorization_request
- [params.shift.last, Hash[params].with_indifferent_access]
+ [params.shift[1], Hash[params].with_indifferent_access]
end
end
@@ -452,7 +464,7 @@ module ActionController
# This removes the `"` characters wrapping the value.
def rewrite_param_values(array_params)
- array_params.each { |param| param.last.gsub! %r/^"|"$/, '' }
+ array_params.each { |param| (param[1] || "").gsub! %r/^"|"$/, '' }
end
# This method takes an authorization body and splits up the key-value
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index acf40b2e16..67875141cb 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -102,16 +102,30 @@ module ActionController
end
end
- @stream.write "data: #{json}\n\n"
+ message = json.gsub("\n", "\ndata: ")
+ @stream.write "data: #{message}\n\n"
end
end
+ class ClientDisconnected < RuntimeError
+ end
+
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
include MonitorMixin
+ # Ignore that the client has disconnected.
+ #
+ # If this value is `true`, calling `write` after the client
+ # disconnects will result in the written content being silently
+ # discarded. If this value is `false` (the default), a
+ # ClientDisconnected exception will be raised.
+ attr_accessor :ignore_disconnect
+
def initialize(response)
@error_callback = lambda { true }
@cv = new_cond
+ @aborted = false
+ @ignore_disconnect = false
super(response, SizedQueue.new(10))
end
@@ -122,6 +136,17 @@ module ActionController
end
super
+
+ unless connected?
+ @buf.clear
+
+ unless @ignore_disconnect
+ # Raise ClientDisconnected, which is a RuntimeError (not an
+ # IOError), because that's more appropriate for something beyond
+ # the developer's control.
+ raise ClientDisconnected, "client disconnected"
+ end
+ end
end
def each
@@ -132,6 +157,10 @@ module ActionController
@response.sent!
end
+ # Write a 'close' event to the buffer; the producer/writing thread
+ # uses this to notify us that it's finished supplying content.
+ #
+ # See also #abort.
def close
synchronize do
super
@@ -140,6 +169,26 @@ module ActionController
end
end
+ # Inform the producer/writing thread that the client has
+ # disconnected; the reading thread is no longer interested in
+ # anything that's being written.
+ #
+ # See also #close.
+ def abort
+ synchronize do
+ @aborted = true
+ @buf.clear
+ end
+ end
+
+ # Is the client still connected and waiting for content?
+ #
+ # The result of calling `write` when this is `false` is determined
+ # by `ignore_disconnect`.
+ def connected?
+ !@aborted
+ end
+
def await_close
synchronize do
@cv.wait_until { @closed }
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..3feb737277 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,15 +62,17 @@ 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
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
- self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
+ self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
end
def _compute_redirect_to_location(options) #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 6c7b4652d4..46405cef55 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.
@@ -73,7 +78,7 @@ module ActionController
# respond_to do |format|
# format.html
# format.csv { render csv: @csvable, filename: @csvable.name }
- # }
+ # end
# end
# To use renderers and their mime types in more concise ways, see
# <tt>ActionController::MimeResponds::ClassMethods.respond_to</tt> and
@@ -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/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 62d5931b45..04401cad7b 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -183,7 +183,7 @@ module ActionController #:nodoc:
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
# Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
#
- # If you are using Unicorn with Nginx, you may need to tweak Nginx.
+ # If you are using Unicorn with NGINX, you may need to tweak NGINX.
# Streaming should work out of the box on Rainbows.
#
# ==== Passenger
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index d86d49c9dc..b70962cf44 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -129,6 +129,10 @@ module ActionController
# Attribute that keeps track of converted arrays, if any, to avoid double
# looping in the common use case permit + mass-assignment. Defined in a
# method to instantiate it only if needed.
+ #
+ # Testing membership still loops, but it's going to be faster than our own
+ # loop that converts values. Also, we are not going to build a new array
+ # object per fetch.
def converted_arrays
@converted_arrays ||= Set.new
end
@@ -158,8 +162,8 @@ module ActionController
def permit!
each_pair do |key, value|
value = convert_hashes_to_parameters(key, value)
- Array.wrap(value).each do |_|
- _.permit! if _.respond_to? :permit!
+ Array.wrap(value).each do |v|
+ v.permit! if v.respond_to? :permit!
end
end
@@ -180,7 +184,12 @@ module ActionController
# ActionController::Parameters.new(person: {}).require(:person)
# # => ActionController::ParameterMissing: param not found: person
def require(key)
- self[key].presence || raise(ParameterMissing.new(key))
+ value = self[key]
+ if value.present? || value == false
+ value
+ else
+ raise ParameterMissing.new(key)
+ end
end
# Alias of #require.
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 754249cbc8..07265be3fe 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -23,16 +23,16 @@ module ActionController
include AbstractController::UrlFor
def url_options
- @_url_options ||= super.reverse_merge(
+ @_url_options ||= {
:host => request.host,
:port => request.optional_port,
:protocol => request.protocol,
- :_recall => request.symbolized_path_parameters
- ).freeze
+ :_recall => request.path_parameters
+ }.merge(super).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 caaebc537a..849286a4a9 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -199,7 +199,7 @@ module ActionController
value = value.dup
end
- if extra_keys.include?(key.to_sym)
+ if extra_keys.include?(key)
non_path_parameters[key] = value
else
if value.is_a?(Array)
@@ -208,7 +208,7 @@ module ActionController
value = value.to_param
end
- path_parameters[key.to_s] = value
+ path_parameters[key] = value
end
end
@@ -550,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
@@ -558,6 +583,7 @@ module ActionController
end
parameters, session, flash = args
+ parameters ||= {}
# Ensure that numbers and symbols passed as params are converted to
# proper params, as is the case when engaging rack.
@@ -576,7 +602,6 @@ module ActionController
@request.env['REQUEST_METHOD'] = http_method
- parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
"anonymous" :
@controller.class.controller_path
@@ -604,8 +629,11 @@ module ActionController
@response.prepare!
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
- @request.session['flash'] = @request.flash.to_session_value
- @request.session.delete('flash') if @request.session['flash'].blank?
+
+ if flash_value = @request.flash.to_session_value
+ @request.session['flash'] = flash_value
+ end
+
@response
end
@@ -670,7 +698,7 @@ module ActionController
:only_path => true,
:action => action,
:relative_url_root => nil,
- :_recall => @request.symbolized_path_parameters)
+ :_recall => @request.path_parameters)
url, query_string = @routes.url_for(options).split("?", 2)
@@ -681,7 +709,7 @@ module ActionController
end
def html_format?(parameters)
- return true unless parameters.is_a?(Hash)
+ return true unless parameters.key?(:format)
Mime.fetch(parameters[:format]) { Mime['html'] }.html?
end
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index f9b278349e..63a3cbc90b 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -92,7 +92,7 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
- SPECIAL_KEYS = %w[extras no-cache max-age public must-revalidate]
+ SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate])
def cache_control_segments
if cache_control = self[CACHE_CONTROL]
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 2666cd4b0a..bc5410dc38 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,34 +1,63 @@
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
- HTTPS AUTH_TYPE GATEWAY_INTERFACE
- PATH_INFO PATH_TRANSLATED QUERY_STRING
- REMOTE_ADDR REMOTE_HOST REMOTE_IDENT REMOTE_USER
- REQUEST_METHOD SCRIPT_NAME
- SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE
- )
+ CGI_VARIABLES = Set.new(%W[
+ AUTH_TYPE
+ CONTENT_LENGTH
+ CONTENT_TYPE
+ GATEWAY_INTERFACE
+ HTTPS
+ PATH_INFO
+ PATH_TRANSLATED
+ QUERY_STRING
+ REMOTE_ADDR
+ REMOTE_HOST
+ REMOTE_IDENT
+ REMOTE_USER
+ REQUEST_METHOD
+ SCRIPT_NAME
+ SERVER_NAME
+ SERVER_PORT
+ SERVER_PROTOCOL
+ SERVER_SOFTWARE
+ ]).freeze
+
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
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 +66,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 +84,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/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index dcb299ed03..5f7627cf96 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -4,10 +4,7 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module Http
module Parameters
- def initialize(env)
- super
- @symbolized_path_params = nil
- end
+ PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
# Returns both GET and POST \parameters in a single hash.
def parameters
@@ -18,20 +15,18 @@ module ActionDispatch
query_parameters.dup
end
params.merge!(path_parameters)
- params.with_indifferent_access
end
end
alias :params :parameters
def path_parameters=(parameters) #:nodoc:
- @symbolized_path_params = nil
- @env.delete("action_dispatch.request.parameters")
- @env["action_dispatch.request.path_parameters"] = parameters
+ @env.delete('action_dispatch.request.parameters')
+ @env[PARAMETERS_KEY] = parameters
end
# The same as <tt>path_parameters</tt> with explicitly symbolized keys.
def symbolized_path_parameters
- @symbolized_path_params ||= path_parameters.symbolize_keys
+ path_parameters
end
# Returns a hash with the \parameters used to form the \path of the request.
@@ -41,11 +36,7 @@ module ActionDispatch
#
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
def path_parameters
- @env["action_dispatch.request.path_parameters"] ||= {}
- end
-
- def reset_parameters #:nodoc:
- @env.delete("action_dispatch.request.parameters")
+ @env[PARAMETERS_KEY] ||= {}
end
private
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index daa06e96e6..4d4b443fb4 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -53,6 +53,17 @@ module ActionDispatch
@uuid = nil
end
+ def check_path_parameters!
+ # If any of the path parameters has an invalid encoding then
+ # raise since it's likely to trigger errors further on.
+ path_parameters.each do |key, value|
+ next unless value.respond_to?(:valid_encoding?)
+ unless value.valid_encoding?
+ raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
+ end
+ end
+ end
+
def key?(key)
@env.key?(key)
end
@@ -64,6 +75,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 +83,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 = {}
@@ -278,7 +291,7 @@ module ActionDispatch
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
+ @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:query, e)
end
@@ -286,7 +299,7 @@ module ActionDispatch
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
+ @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
rescue TypeError => e
raise ActionController::BadRequest.new(:request, e)
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 3d27ff2b24..2fab6be1a5 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -97,6 +97,9 @@ module ActionDispatch # :nodoc:
x
end
+ def abort
+ end
+
def close
@response.commit!
@closed = true
@@ -207,18 +210,6 @@ module ActionDispatch # :nodoc:
end
alias_method :status_message, :message
- def respond_to?(method, include_private = false)
- if method.to_s == 'to_path'
- stream.respond_to?(method)
- else
- super
- end
- end
-
- def to_path
- stream.to_path
- end
-
# Returns the content of the response as a string. This contains the contents
# of any calls to <tt>render</tt>.
def body
@@ -271,6 +262,17 @@ module ActionDispatch # :nodoc:
stream.close if stream.respond_to?(:close)
end
+ def abort
+ if stream.respond_to?(:abort)
+ stream.abort
+ elsif stream.respond_to?(:close)
+ # `stream.close` should really be reserved for a close from the
+ # other direction, but we must fall back to it for
+ # compatibility.
+ stream.close
+ end
+ end
+
# Turns the Response into a Rack-compatible array of the status, headers,
# and body.
def to_a
@@ -296,6 +298,9 @@ module ActionDispatch # :nodoc:
cookies
end
+ def _status_code
+ @status
+ end
private
def before_committed
@@ -334,6 +339,38 @@ module ActionDispatch # :nodoc:
!@sending_file && @charset != false
end
+ class RackBody
+ def initialize(response)
+ @response = response
+ end
+
+ def each(*args, &block)
+ @response.each(*args, &block)
+ end
+
+ def close
+ # Rack "close" maps to Response#abort, and *not* Response#close
+ # (which is used when the controller's finished writing)
+ @response.abort
+ end
+
+ def body
+ @response.body
+ end
+
+ def respond_to?(method, include_private = false)
+ if method.to_s == 'to_path'
+ @response.stream.respond_to?(method)
+ else
+ super
+ end
+ end
+
+ def to_path
+ @response.stream.to_path
+ end
+ end
+
def rack_response(status, header)
assign_default_content_type_and_charset!(header)
handle_conditional_get!
@@ -344,7 +381,7 @@ module ActionDispatch # :nodoc:
header.delete CONTENT_TYPE
[status, header, []]
else
- [status, header, Rack::BodyProxy.new(self){}]
+ [status, header, RackBody.new(self)]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 6f5a52c568..a5858758c6 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -5,99 +5,114 @@ module ActionDispatch
module Http
module URL
IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
- HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/
+ HOST_REGEXP = /(^[^:]+:\/\/)?([^:]+)(?::(\d+$))?/
PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
mattr_accessor :tld_length
self.tld_length = 1
class << self
- def extract_domain(host, tld_length = @@tld_length)
- host.split('.').last(1 + tld_length).join('.') if named_host?(host)
+ def extract_domain(host, tld_length)
+ extract_domain_from(host, tld_length) if named_host?(host)
end
- def extract_subdomains(host, tld_length = @@tld_length)
+ def extract_subdomains(host, tld_length)
if named_host?(host)
- parts = host.split('.')
- parts[0..-(tld_length + 2)]
+ extract_subdomains_from(host, tld_length)
else
[]
end
end
- def extract_subdomain(host, tld_length = @@tld_length)
+ def extract_subdomain(host, tld_length)
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
-
- params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
- params.reject! { |_,v| v.to_param.nil? }
-
- result = build_host_url(options)
- if options[:trailing_slash]
- if path.include?('?')
- result << path.sub(/\?/, '/\&')
- else
- result << path.sub(/[^\/]\z|\A\z/, '\&/')
- end
- else
- result << path
+ 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
- result << "?#{params.to_query}" unless params.empty?
- result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- result
- end
- private
+ path = options[:script_name].to_s.chomp("/")
+ path << options[:path].to_s
- 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'
- end
+ add_trailing_slash(path) if options[:trailing_slash]
- result = ""
+ result = path
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 << rewrite_authentication(options)
- result << options[:host]
- result << ":#{options[:port]}" if options[:port]
+ 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
- def named_host?(host)
- host && IP_HOST_REGEXP !~ host
+ private
+
+ def extract_domain_from(host, tld_length)
+ host.split('.').last(1 + tld_length).join('.')
end
- def same_host?(options)
- (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil?
+ def extract_subdomains_from(host, tld_length)
+ parts = host.split('.')
+ parts[0..-(tld_length + 2)]
end
- def rewrite_authentication(options)
+ def add_trailing_slash(path)
+ # includes querysting
+ if path.include?('?')
+ path.sub!(/\?/, '/\&')
+ # does not have a .format
+ elsif !path.include?(".")
+ path.sub!(/[^\/]\z|\A\z/, '\&/')
+ end
+
+ path
+ end
+
+ def build_host_url(options)
+ protocol = options[:protocol]
+ host = options[:host]
+ port = options[:port]
+ if match = host.match(HOST_REGEXP)
+ protocol ||= match[1] unless protocol == false
+ host = match[2]
+ port = match[3] unless options.key? :port
+ end
+
+ protocol = normalize_protocol protocol
+ host = normalize_host(host, options)
+
+ result = protocol.dup
+
if options[:user] && options[:password]
- "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
- else
- ""
+ result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
end
+
+ result << host
+ normalize_port(port, protocol) { |normalized_port|
+ result << ":#{normalized_port}"
+ }
+
+ result
end
- def normalize_protocol(options)
- case options[:protocol]
+ def named_host?(host)
+ IP_HOST_REGEXP !~ host
+ end
+
+ def normalize_protocol(protocol)
+ case protocol
when nil
"http://"
when false, "//"
@@ -105,36 +120,39 @@ module ActionDispatch
when PROTOCOL_REGEXP
"#{$1}://"
else
- raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}"
+ raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
end
end
- def normalize_host(options)
- return options[:host] if !named_host?(options[:host]) || same_host?(options)
+ def normalize_host(_host, options)
+ return _host unless named_host?(_host)
tld_length = options[:tld_length] || @@tld_length
+ subdomain = options.fetch :subdomain, true
+ domain = options[:domain]
host = ""
- if options[:subdomain] == true || !options.key?(:subdomain)
- host << extract_subdomain(options[:host], tld_length).to_param
- elsif options[:subdomain].present?
- host << options[:subdomain].to_param
+ if subdomain == true
+ return _host if domain.nil?
+
+ host << extract_subdomains_from(_host, tld_length).join('.')
+ elsif subdomain
+ host << subdomain.to_param
end
host << "." unless host.empty?
- host << (options[:domain] || extract_domain(options[:host], tld_length))
+ host << (domain || extract_domain_from(_host, tld_length))
host
end
- def normalize_port(options)
- return nil if options[:port].nil? || options[:port] == false
+ def normalize_port(port, protocol)
+ return unless port
- case options[:protocol]
- when "//"
- nil
+ case protocol
+ when "//" then yield port
when "https://"
- options[:port].to_i == 443 ? nil : options[:port]
+ yield port unless port.to_i == 443
else
- options[:port].to_i == 80 ? nil : options[:port]
+ yield port unless port.to_i == 80
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 57f0963731..6d58323789 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 = []
@@ -30,6 +30,12 @@ module ActionDispatch
parameterized_parts.key?(key) || route.defaults.key?(key)
end
+ defaults = route.defaults
+ required_parts = route.required_parts
+ parameterized_parts.delete_if do |key, value|
+ value.to_s == defaults[key].to_s && !required_parts.include?(key)
+ end
+
return [route.format(parameterized_parts), params]
end
@@ -74,12 +80,12 @@ module ActionDispatch
if named_routes.key?(name)
yield named_routes[name]
else
- routes = non_recursive(cache, options.to_a)
+ routes = non_recursive(cache, options)
hash = routes.group_by { |_, r| r.score(options) }
hash.keys.sort.reverse_each do |score|
- next if score < 0
+ break if score < 0
hash[score].sort_by { |i, _| i }.each do |_, route|
yield route
@@ -90,14 +96,14 @@ module ActionDispatch
def non_recursive(cache, options)
routes = []
- stack = [cache]
+ queue = [cache]
- while stack.any?
- c = stack.shift
+ while queue.any?
+ c = queue.shift
routes.concat(c[:___routes]) if c.key?(:___routes)
options.each do |pair|
- stack << c[pair] if c.key?(pair)
+ queue << c[pair] if c.key?(pair)
end
end
@@ -126,11 +132,6 @@ module ActionDispatch
}
end
- # Returns +true+ if no missing keys are present, otherwise +false+.
- def verify_required_parts!(route, parts)
- missing_keys(route, parts).empty?
- end
-
def build_cache
root = { ___routes: [] }
routes.each_with_index do |route, i|
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 935442ef66..bb01c087bc 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -93,6 +93,10 @@ module ActionDispatch
class Star < Unary # :nodoc:
def type; :STAR; end
+
+ def name
+ left.name.tr '*:', ''
+ end
end
class Binary < Node # :nodoc:
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..3af940a02f 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -1,27 +1,20 @@
+require 'action_dispatch/journey/router/strexp'
+
module ActionDispatch
module Journey # :nodoc:
module Path # :nodoc:
class Pattern # :nodoc:
attr_reader :spec, :requirements, :anchored
- def initialize(strexp)
- parser = Journey::Parser.new
-
- @anchored = true
+ def self.from_string string
+ new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
+ end
- case strexp
- when String
- @spec = parser.parse(strexp)
- @requirements = {}
- @separators = "/.?"
- when Router::Strexp
- @spec = parser.parse(strexp.path)
- @requirements = strexp.requirements
- @separators = strexp.separators.join
- @anchored = strexp.anchor
- else
- raise ArgumentError, "Bad expression: #{strexp}"
- end
+ def initialize(strexp)
+ @spec = strexp.ast
+ @requirements = strexp.requirements
+ @separators = strexp.separators.join
+ @anchored = strexp.anchor
@names = nil
@optional_names = nil
@@ -30,6 +23,10 @@ module ActionDispatch
@offsets = nil
end
+ def build_formatter
+ Visitors::FormatBuilder.new.accept(spec)
+ end
+
def ast
@spec.grep(Nodes::Symbol).each do |node|
re = @requirements[node.to_sym]
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index c8eb0f6f2d..9f0a3af902 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -16,14 +16,6 @@ module ActionDispatch
@app = app
@path = path
- # Unwrap any constraints so we can see what's inside for route generation.
- # This allows the formatter to skip over any mounted applications or redirects
- # that shouldn't be matched when using a url_for without a route name.
- while app.is_a?(Routing::Mapper::Constraints) do
- app = app.app
- end
- @dispatcher = app.is_a?(Routing::RouteSet::Dispatcher)
-
@constraints = constraints
@defaults = defaults
@required_defaults = nil
@@ -31,6 +23,7 @@ module ActionDispatch
@parts = nil
@decorated_ast = nil
@precedence = 0
+ @path_formatter = @path.build_formatter
end
def ast
@@ -72,15 +65,7 @@ module ActionDispatch
alias :segment_keys :parts
def format(path_options)
- path_options.delete_if do |key, value|
- value.to_s == defaults[key].to_s && !required_parts.include?(key)
- end
-
- Visitors::Formatter.new(path_options).accept(path.spec)
- end
-
- def optimized_path
- Visitors::OptimizedPath.new.accept(path.spec)
+ @path_formatter.evaluate path_options
end
def optional_parts
@@ -101,8 +86,12 @@ module ActionDispatch
end
end
+ def glob?
+ !path.spec.grep(Nodes::Star).empty?
+ end
+
def dispatcher?
- @dispatcher
+ @app.dispatcher?
end
def matches?(request)
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 36561c71a1..fe3fc0a9fa 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -20,60 +20,32 @@ module ActionDispatch
# :nodoc:
VERSION = '2.0.0'
- class NullReq # :nodoc:
- attr_reader :env
- def initialize(env)
- @env = env
- end
-
- def request_method
- env['REQUEST_METHOD']
- end
-
- def path_info
- env['PATH_INFO']
- end
-
- def ip
- env['REMOTE_ADDR']
- end
-
- def [](k)
- env[k]
- end
- end
-
- attr_reader :request_class, :formatter
attr_accessor :routes
- def initialize(routes, options)
- @options = options
- @params_key = options[:parameters_key]
- @request_class = options[:request_class] || NullReq
- @routes = routes
+ def initialize(routes)
+ @routes = routes
end
- def call(env)
- env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
-
- find_routes(env).each do |match, parameters, route|
- script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
- 'PATH_INFO',
- @params_key)
+ def serve(req)
+ find_routes(req).each do |match, parameters, route|
+ set_params = req.path_parameters
+ path_info = req.path_info
+ script_name = req.script_name
unless route.path.anchored
- env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
- env['PATH_INFO'] = match.post_match
+ req.script_name = (script_name.to_s + match.to_s).chomp('/')
+ req.path_info = match.post_match
+ req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
end
- env[@params_key] = (set_params || {}).merge parameters
+ req.path_parameters = set_params.merge parameters
- status, headers, body = route.app.call(env)
+ status, headers, body = route.app.serve(req)
if 'pass' == headers['X-Cascade']
- env['SCRIPT_NAME'] = script_name
- env['PATH_INFO'] = path_info
- env[@params_key] = set_params
+ req.script_name = script_name
+ req.path_info = path_info
+ req.path_parameters = set_params
next
end
@@ -83,14 +55,14 @@ module ActionDispatch
return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
end
- def recognize(req)
- find_routes(req.env).each do |match, parameters, route|
+ def recognize(rails_req)
+ find_routes(rails_req).each do |match, parameters, route|
unless route.path.anchored
- req.env['SCRIPT_NAME'] = match.to_s
- req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
+ rails_req.script_name = match.to_s
+ rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1')
end
- yield(route, nil, parameters)
+ yield(route, parameters)
end
end
@@ -124,29 +96,30 @@ module ActionDispatch
simulator.memos(path) { [] }
end
- def find_routes env
- req = request_class.new(env)
-
+ def find_routes req
routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
r.path.match(req.path_info)
}
- routes.concat get_routes_as_head(routes)
+
+ if req.env["REQUEST_METHOD"] === "HEAD"
+ routes.concat get_routes_as_head(routes)
+ end
routes.sort_by!(&:precedence).select! { |r| r.matches?(req) }
routes.map! { |r|
match_data = r.path.match(req.path_info)
- match_names = match_data.names.map { |n| n.to_sym }
- match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
- info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
-
- [match_data, r.defaults.merge(info), r]
+ path_parameters = r.defaults.dup
+ match_data.names.zip(match_data.captures) { |name,val|
+ path_parameters[name.to_sym] = Utils.unescape_uri(val) if val
+ }
+ [match_data, path_parameters, r]
}
end
def get_routes_as_head(routes)
precedence = (routes.map(&:precedence).max || 0) + 1
- routes = routes.select { |r|
+ routes.select { |r|
r.verb === "GET" && !(r.verb === "HEAD")
}.map! { |r|
Route.new(r.name,
@@ -157,8 +130,6 @@ module ActionDispatch
route.precedence = r.precedence + precedence
end
}
- routes.flatten!
- routes
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/router/strexp.rb b/actionpack/lib/action_dispatch/journey/router/strexp.rb
index f97f1a223e..4b7738f335 100644
--- a/actionpack/lib/action_dispatch/journey/router/strexp.rb
+++ b/actionpack/lib/action_dispatch/journey/router/strexp.rb
@@ -6,18 +6,21 @@ module ActionDispatch
alias :compile :new
end
- attr_reader :path, :requirements, :separators, :anchor
+ attr_reader :path, :requirements, :separators, :anchor, :ast
- def initialize(path, requirements, separators, anchor = true)
+ def self.build(path, requirements, separators, anchor = true)
+ parser = Journey::Parser.new
+ ast = parser.parse path
+ new ast, path, requirements, separators, anchor
+ end
+
+ def initialize(ast, path, requirements, separators, anchor = true)
+ @ast = ast
@path = path
@requirements = requirements
@separators = separators
@anchor = anchor
end
-
- def names
- @path.scan(/:\w+/).map { |s| s.tr(':', '') }
- end
end
end
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..52b4c8b489 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -1,14 +1,57 @@
# 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[param.name]
+ return ''.freeze unless value
+ 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 +81,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 +125,8 @@ module ActionDispatch
end
def visit(node)
- super
block.call(node)
+ super
end
end
@@ -77,77 +150,6 @@ module ActionDispatch
end
end
- class OptimizedPath < Visitor # :nodoc:
- def accept(node)
- Array(visit(node))
- end
-
- private
-
- def visit_CAT(node)
- [visit(node.left), visit(node.right)].flatten
- end
-
- def visit_SYMBOL(node)
- node.left[1..-1].to_sym
- end
-
- def visit_STAR(node)
- visit(node.left)
- end
-
- def visit_GROUP(node)
- []
- end
-
- %w{ LITERAL SLASH DOT }.each do |t|
- class_eval %{ def visit_#{t}(n); n.left; end }, __FILE__, __LINE__
- 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/journey/visualizer/index.html.erb b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb
index 6aff10956a..9b28a65200 100644
--- a/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb
+++ b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb
@@ -2,13 +2,13 @@
<html>
<head>
<title><%= title %></title>
- <link rel="stylesheet" href="https://raw.github.com/gist/1706081/af944401f75ea20515a02ddb3fb43d23ecb8c662/reset.css" type="text/css">
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.css" type="text/css">
<style>
<% stylesheets.each do |style| %>
<%= style %>
<% end %>
</style>
- <script src="https://raw.github.com/gist/1706081/df464722a05c3c2bec450b7b5c8240d9c31fa52d/d3.min.js" type="text/javascript"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.8/d3.min.js" type="text/javascript"></script>
</head>
<body>
<div id="wrapper">
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..6a79b4e859 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -11,7 +11,7 @@ module ActionDispatch
# Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
# requires. Some Rack servers simply drop preceding headers, and only report
# the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
- # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
+ # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
# then you should test your Rack server to make sure your data is good.
#
# IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
@@ -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
@@ -118,7 +118,7 @@ module ActionDispatch
#
# REMOTE_ADDR will be correct if the request is made directly against the
# Ruby process, on e.g. Heroku. When the request is proxied by another
- # server like HAProxy or Nginx, the IP address that made the original
+ # server like HAProxy or NGINX, the IP address that made the original
# request will be put in an X-Forwarded-For header. If there are multiple
# proxies, that header may contain a list of IPs. Other proxy services
# set the Client-Ip header instead, so we check that too.
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 cce0d75af4..6ffa242da4 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -148,8 +148,8 @@
// On key press perform a search for matching paths
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>',
+ defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + escape(sanitizePath(userInput)) +'):</th></tr>',
+ defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + escape(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>';
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 9cd884daa3..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
diff --git a/actionpack/lib/action_dispatch/routing/endpoint.rb b/actionpack/lib/action_dispatch/routing/endpoint.rb
new file mode 100644
index 0000000000..88aa13c3e8
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/endpoint.rb
@@ -0,0 +1,10 @@
+module ActionDispatch
+ module Routing
+ class Endpoint # :nodoc:
+ def dispatcher?; false; end
+ def redirect?; false; end
+ def matches?(req); true; end
+ def app; self; end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index 71a0c5e826..ea3b2f419d 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -5,22 +5,15 @@ module ActionDispatch
module Routing
class RouteWrapper < SimpleDelegator
def endpoint
- rack_app ? rack_app.inspect : "#{controller}##{action}"
+ app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect
end
def constraints
requirements.except(:controller, :action)
end
- def rack_app(app = self.app)
- @rack_app ||= begin
- class_name = app.class.name.to_s
- if class_name == "ActionDispatch::Routing::Mapper::Constraints"
- rack_app(app.app)
- elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
- app
- end
- end
+ def rack_app
+ app.app
end
def verb
@@ -73,7 +66,7 @@ module ActionDispatch
end
def engine?
- rack_app && rack_app.respond_to?(:routes)
+ rack_app.respond_to?(:routes)
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 77718a14c1..aac5546aa1 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -6,6 +6,8 @@ require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/module/remove_method'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
+require 'action_dispatch/routing/endpoint'
+require 'active_support/deprecation'
module ActionDispatch
module Routing
@@ -15,121 +17,189 @@ module ActionDispatch
:controller, :action, :path_names, :constraints,
:shallow, :blocks, :defaults, :options]
- class Constraints #:nodoc:
- def self.new(app, constraints, request = Rack::Request)
- if constraints.any?
- super(app, constraints, request)
- else
- app
+ class Constraints < Endpoint #:nodoc:
+ attr_reader :app, :constraints
+
+ def initialize(app, constraints, dispatcher_p)
+ # Unwrap Constraints objects. I don't actually think it's possible
+ # to pass a Constraints object to this constructor, but there were
+ # multiple places that kept testing children of this object. I
+ # *think* they were just being defensive, but I have no idea.
+ if app.is_a?(self.class)
+ constraints += app.constraints
+ app = app.app
end
- end
- attr_reader :app, :constraints
+ @dispatcher = dispatcher_p
- def initialize(app, constraints, request)
- @app, @constraints, @request = app, constraints, request
+ @app, @constraints, = app, constraints
end
- def matches?(env)
- req = @request.new(env)
+ def dispatcher?; @dispatcher; end
+ def matches?(req)
@constraints.all? do |constraint|
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
end
- ensure
- req.reset_parameters
end
- def call(env)
- matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
+ def serve(req)
+ return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
+
+ if dispatcher?
+ @app.serve req
+ else
+ @app.call req.env
+ end
end
private
def constraint_args(constraint, request)
- constraint.arity == 1 ? [request] : [request.symbolized_path_parameters, request]
+ constraint.arity == 1 ? [request] : [request.path_parameters, request]
end
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
- WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
- attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
+ attr_reader :requirements, :conditions, :defaults
+ attr_reader :to, :default_controller, :default_action, :as, :anchor
+
+ def self.build(scope, path, options)
+ options = scope[:options].merge(options) if scope[:options]
- def initialize(set, scope, path, options)
- @set, @scope, @path, @options = set, scope, path, options
- @requirements, @conditions, @defaults = {}, {}, {}
+ options.delete :only
+ options.delete :except
+ options.delete :shallow_path
+ options.delete :shallow_prefix
+ options.delete :shallow
- normalize_options!
- normalize_path!
- normalize_requirements!
- normalize_conditions!
- normalize_defaults!
+ defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
+
+ new scope, path, defaults, options
+ end
+
+ def initialize(scope, path, defaults, options)
+ @requirements, @conditions = {}, {}
+ @defaults = defaults
+
+ @to = options.delete :to
+ @default_controller = options.delete(:controller) || scope[:controller]
+ @default_action = options.delete(:action) || scope[:action]
+ @as = options.delete :as
+ @anchor = options.delete :anchor
+
+ formatted = options.delete :format
+ via = Array(options.delete(:via) { [] })
+ options_constraints = options.delete :constraints
+
+ path = normalize_path! path, formatted
+ ast = path_ast path
+ path_params = path_params ast
+
+ options = normalize_options!(options, formatted, path_params, ast, scope[:module])
+
+
+ split_constraints(path_params, scope[:constraints]) if scope[:constraints]
+ constraints = constraints(options, path_params)
+
+ split_constraints path_params, constraints
+
+ @blocks = blocks(options_constraints, scope[:blocks])
+
+ if options_constraints.is_a?(Hash)
+ split_constraints path_params, options_constraints
+ options_constraints.each do |key, default|
+ if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
+ @defaults[key] ||= default
+ end
+ end
+ end
+
+ normalize_format!(formatted)
+
+ @conditions[:path_info] = path
+ @conditions[:parsed_path_info] = ast
+
+ add_request_method(via, @conditions)
+ normalize_defaults!(options)
end
def to_route
- [ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
+ [ app(@blocks), conditions, requirements, defaults, as, anchor ]
end
private
- def normalize_path!
- raise ArgumentError, "path is required" if @path.blank?
- @path = Mapper.normalize_path(@path)
+ def normalize_path!(path, format)
+ path = Mapper.normalize_path(path)
- if required_format?
- @path = "#{@path}.:format"
- elsif optional_format?
- @path = "#{@path}(.:format)"
+ if format == true
+ "#{path}.:format"
+ elsif optional_format?(path, format)
+ "#{path}(.:format)"
+ else
+ path
end
end
- def required_format?
- options[:format] == true
+ def optional_format?(path, format)
+ format != false && !path.include?(':format') && !path.end_with?('/')
end
- def optional_format?
- options[:format] != false && !path.include?(':format') && !path.end_with?('/')
- end
-
- def normalize_options!
- @options.reverse_merge!(scope[:options]) if scope[:options]
- path_without_format = path.sub(/\(\.:format\)$/, '')
-
+ def normalize_options!(options, formatted, path_params, path_ast, modyoule)
# Add a constraint for wildcard route to make it non-greedy and match the
# optional format part of the route by default
- if path_without_format.match(WILDCARD_PATH) && @options[:format] != false
- @options[$1.to_sym] ||= /.+?/
+ if formatted != false
+ path_ast.grep(Journey::Nodes::Star) do |node|
+ options[node.name.to_sym] ||= /.+?/
+ end
end
- if path_without_format.match(':controller')
- raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
+ if path_params.include?(:controller)
+ raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
# Add a default constraint for :controller path segments that matches namespaced
# controllers with default routes like :controller/:action/:id(.:format), e.g:
# GET /admin/products/show/1
# => { controller: 'admin/products', action: 'show', id: '1' }
- @options[:controller] ||= /.+?/
+ options[:controller] ||= /.+?/
end
- @options.merge!(default_controller_and_action)
+ if to.respond_to? :call
+ options
+ else
+ to_endpoint = split_to to
+ controller = to_endpoint[0] || default_controller
+ action = to_endpoint[1] || default_action
+
+ controller = add_controller_module(controller, modyoule)
+
+ options.merge! check_controller_and_action(path_params, controller, action)
+ end
end
- def normalize_requirements!
- constraints.each do |key, requirement|
- next unless segment_keys.include?(key) || key == :controller
- verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
- @requirements[key] = requirement
+ def split_constraints(path_params, constraints)
+ constraints.each_pair do |key, requirement|
+ if path_params.include?(key) || key == :controller
+ verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
+ @requirements[key] = requirement
+ else
+ @conditions[key] = requirement
+ end
end
+ end
- if options[:format] == true
+ def normalize_format!(formatted)
+ if formatted == true
@requirements[:format] ||= /.+/
- elsif Regexp === options[:format]
- @requirements[:format] = options[:format]
- elsif String === options[:format]
- @requirements[:format] = Regexp.compile(options[:format])
+ elsif Regexp === formatted
+ @requirements[:format] = formatted
+ @defaults[:format] = nil
+ elsif String === formatted
+ @requirements[:format] = Regexp.compile(formatted)
+ @defaults[:format] = formatted
end
end
@@ -143,169 +213,143 @@ module ActionDispatch
end
end
- def normalize_defaults!
- @defaults.merge!(scope[:defaults]) if scope[:defaults]
- @defaults.merge!(options[:defaults]) if options[:defaults]
-
- options.each do |key, default|
- unless Regexp === default || IGNORE_OPTIONS.include?(key)
+ def normalize_defaults!(options)
+ options.each_pair do |key, default|
+ unless Regexp === default
@defaults[key] = default
end
end
-
- if options[:constraints].is_a?(Hash)
- options[:constraints].each do |key, default|
- if URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
- @defaults[key] ||= default
- end
- end
- end
-
- if Regexp === options[:format]
- @defaults[:format] = nil
- elsif String === options[:format]
- @defaults[:format] = options[:format]
- end
end
- def normalize_conditions!
- @conditions[:path_info] = path
-
- constraints.each do |key, condition|
- unless segment_keys.include?(key) || key == :controller
- @conditions[key] = condition
- end
- end
-
- required_defaults = []
- options.each do |key, required_default|
- unless segment_keys.include?(key) || IGNORE_OPTIONS.include?(key) || Regexp === required_default
- required_defaults << key
- 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
- @conditions[:required_defaults] = required_defaults
+ end
- via_all = options.delete(:via) if options[:via] == :all
+ def add_request_method(via, conditions)
+ return if via == [:all]
- if !via_all && options[:via].blank?
+ if via.empty?
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
"If you want to expose your action to GET, use `get` in the router:\n" \
" Instead of: match \"controller#action\"\n" \
" Do: get \"controller#action\""
- raise msg
+ raise ArgumentError, msg
end
- if via = options[:via]
- @conditions[:request_method] = Array(via).map { |m| m.to_s.dasherize.upcase }
- end
+ conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
end
- def app
- Constraints.new(endpoint, blocks, @set.request_class)
- end
+ def app(blocks)
+ return to if Redirect === to
- def default_controller_and_action
if to.respond_to?(:call)
- { }
+ Constraints.new(to, blocks, false)
else
- if to.is_a?(String)
- controller, action = to.split('#')
- elsif to.is_a?(Symbol)
- action = to.to_s
- end
-
- controller ||= default_controller
- action ||= default_action
-
- if @scope[:module] && !controller.is_a?(Regexp)
- if controller =~ %r{\A/}
- controller = controller[1..-1]
- else
- controller = [@scope[:module], controller].compact.join("/").presence
- end
- end
-
- if controller.is_a?(String) && controller =~ %r{\A/}
- raise ArgumentError, "controller name should not start with a slash"
+ if blocks.any?
+ Constraints.new(dispatcher, blocks, true)
+ else
+ dispatcher
end
+ end
+ end
- controller = controller.to_s unless controller.is_a?(Regexp)
- action = action.to_s unless action.is_a?(Regexp)
+ def check_controller_and_action(path_params, controller, action)
+ hash = check_part(:controller, controller, path_params, {}) do |part|
+ translate_controller(part) {
+ message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
+ message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
- if controller.blank? && segment_keys.exclude?(:controller)
- message = "Missing :controller key on routes definition, please check your routes."
raise ArgumentError, message
- end
+ }
+ end
- if action.blank? && segment_keys.exclude?(:action)
- message = "Missing :action key on routes definition, please check your routes."
- raise ArgumentError, message
- end
+ check_part(:action, action, path_params, hash) { |part|
+ part.is_a?(Regexp) ? part : part.to_s
+ }
+ end
- if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
- message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
- message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
+ def check_part(name, part, path_params, hash)
+ if part
+ hash[name] = yield(part)
+ else
+ unless path_params.include?(name)
+ message = "Missing :#{name} key on routes definition, please check your routes."
raise ArgumentError, message
end
-
- hash = {}
- hash[:controller] = controller unless controller.blank?
- hash[:action] = action unless action.blank?
- hash
end
- end
-
- def blocks
- if options[:constraints].present? && !options[:constraints].is_a?(Hash)
- [options[:constraints]]
+ hash
+ end
+
+ def split_to(to)
+ case to
+ when Symbol
+ ActiveSupport::Deprecation.warn "defining a route where `to` is a symbol is deprecated. Please change \"to: :#{to}\" to \"action: :#{to}\""
+ [nil, to.to_s]
+ when /#/ then to.split('#')
+ when String
+ ActiveSupport::Deprecation.warn "defining a route where `to` is a controller without an action is deprecated. Please change \"to: :#{to}\" to \"controller: :#{to}\""
+ [to, nil]
else
- scope[:blocks] || []
+ []
end
end
- def constraints
- @constraints ||= {}.tap do |constraints|
- constraints.merge!(scope[:constraints]) if scope[:constraints]
-
- options.except(*IGNORE_OPTIONS).each do |key, option|
- constraints[key] = option if Regexp === option
+ def add_controller_module(controller, modyoule)
+ if modyoule && !controller.is_a?(Regexp)
+ if controller =~ %r{\A/}
+ controller[1..-1]
+ else
+ [modyoule, controller].compact.join("/")
end
-
- constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
+ else
+ controller
end
end
- def segment_keys
- @segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
- end
+ def translate_controller(controller)
+ return controller if Regexp === controller
+ return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
- def path_pattern
- Journey::Path::Pattern.new(strexp)
- end
-
- def strexp
- Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
+ yield
end
- def endpoint
- to.respond_to?(:call) ? to : dispatcher
+ def blocks(options_constraints, scope_blocks)
+ if options_constraints && !options_constraints.is_a?(Hash)
+ verify_callable_constraint(options_constraints)
+ [options_constraints]
+ else
+ scope_blocks || []
+ end
end
- def dispatcher
- Routing::RouteSet::Dispatcher.new(:defaults => defaults)
+ def constraints(options, path_params)
+ constraints = {}
+ required_defaults = []
+ options.each_pair do |key, option|
+ if Regexp === option
+ constraints[key] = option
+ else
+ required_defaults << key unless path_params.include?(key)
+ end
+ end
+ @conditions[:required_defaults] = required_defaults
+ constraints
end
- def to
- options[:to]
+ def path_params(ast)
+ ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
end
- def default_controller
- options[:controller] || scope[:controller]
+ def path_ast(path)
+ parser = Journey::Parser.new
+ parser.parse path
end
- def default_action
- options[:action] || scope[:action]
+ def dispatcher
+ Routing::RouteSet::Dispatcher.new(defaults)
end
end
@@ -340,18 +384,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 +424,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
@@ -391,13 +451,19 @@ module ActionDispatch
# [:action]
# The route's action.
#
+ # [:param]
+ # Overrides the default resource identifier `:id` (name of the
+ # dynamic segment used to generate the routes).
+ # You can access that segment from your controller using
+ # <tt>params[<:param>]</tt>.
+ #
# [:path]
# The path prefix for the routes.
#
# [: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 +482,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 +509,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 +525,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 +534,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 +620,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
@@ -1403,7 +1468,20 @@ module ActionDispatch
if rest.empty? && Hash === path
options = path
path, to = options.find { |name, _value| name.is_a?(String) }
- options[:to] = to
+
+ case to
+ when Symbol
+ options[:action] = to
+ when String
+ if to =~ /#/
+ options[:to] = to
+ else
+ options[:controller] = to
+ end
+ else
+ options[:to] = to
+ end
+
options.delete(path)
paths = [path]
else
@@ -1457,6 +1535,8 @@ module ActionDispatch
def add_route(action, options) # :nodoc:
path = path_for_action(action, options.delete(:path))
+ raise ArgumentError, "path is required" if path.blank?
+
action = action.to_s.dup
if action =~ /^[\w\-\/]+$/
@@ -1471,7 +1551,7 @@ module ActionDispatch
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
+ mapping = Mapping.build(@scope, URI.parser.escape(path), options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
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/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index b08e62543b..3c1c4fadf6 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -3,10 +3,11 @@ require 'active_support/core_ext/uri'
require 'active_support/core_ext/array/extract_options'
require 'rack/utils'
require 'action_controller/metal/exceptions'
+require 'action_dispatch/routing/endpoint'
module ActionDispatch
module Routing
- class Redirect # :nodoc:
+ class Redirect < Endpoint # :nodoc:
attr_reader :status, :block
def initialize(status, block)
@@ -14,18 +15,15 @@ module ActionDispatch
@block = block
end
- def call(env)
- req = Request.new(env)
+ def redirect?; true; end
- # If any of the path parameters has an invalid encoding then
- # raise since it's likely to trigger errors further on.
- req.symbolized_path_parameters.each do |key, value|
- unless value.valid_encoding?
- raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
- end
- end
+ def call(env)
+ serve Request.new env
+ end
- uri = URI.parse(path(req.symbolized_path_parameters, req))
+ def serve(req)
+ req.check_path_parameters!
+ uri = URI.parse(path(req.path_parameters, req))
unless uri.host
if relative_path?(uri.path)
@@ -39,7 +37,7 @@ module ActionDispatch
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
- body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
+ body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>)
headers = {
'Location' => uri.to_s,
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index a03fb4cee7..69535faabd 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,11 +1,14 @@
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'
+require 'action_dispatch/routing/endpoint'
module ActionDispatch
module Routing
@@ -16,27 +19,17 @@ module ActionDispatch
# alias inspect to to_s.
alias inspect to_s
- PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
-
- class Dispatcher #:nodoc:
- def initialize(options={})
- @defaults = options[:defaults]
- @glob_param = options.delete(:glob)
+ class Dispatcher < Routing::Endpoint #:nodoc:
+ def initialize(defaults)
+ @defaults = defaults
@controller_class_names = ThreadSafe::Cache.new
end
- def call(env)
- params = env[PARAMETERS_KEY]
-
- # If any of the path parameters has an invalid encoding then
- # raise since it's likely to trigger errors further on.
- params.each do |key, value|
- next unless value.respond_to?(:valid_encoding?)
+ def dispatcher?; true; end
- unless value.valid_encoding?
- raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
- end
- end
+ def serve(req)
+ req.check_path_parameters!
+ params = req.path_parameters
prepare_params!(params)
@@ -45,13 +38,12 @@ module ActionDispatch
return [404, {'X-Cascade' => 'pass'}, []]
end
- dispatch(controller, params[:action], env)
+ dispatch(controller, params[:action], req.env)
end
def prepare_params!(params)
normalize_controller!(params)
merge_default_action!(params)
- split_glob_param!(params) if @glob_param
end
# If this is a default_controller (i.e. a controller specified by the user)
@@ -87,10 +79,6 @@ module ActionDispatch
def merge_default_action!(params)
params[:action] ||= 'index'
end
-
- def split_glob_param!(params)
- params[@glob_param] = params[@glob_param].split('/').map { |v| URI.parser.unescape(v) }
- end
end
# A NamedRouteCollection instance is a collection of named routes, and also
@@ -155,7 +143,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:
@@ -163,16 +151,13 @@ module ActionDispatch
def initialize(route, options)
super
- @klass = Journey::Router::Utils
@required_parts = @route.required_parts
@arg_size = @required_parts.size
- @optimized_path = @route.optimized_path
end
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
@@ -183,18 +168,14 @@ module ActionDispatch
private
def optimized_helper(args)
- params = Hash[parameterize_args(args)]
+ params = parameterize_args(args)
missing_keys = missing_keys(params)
unless missing_keys.empty?
raise_generation_error(params, missing_keys)
end
- @optimized_path.map{ |segment| replace_segment(params, segment) }.join
- end
-
- def replace_segment(params, segment)
- Symbol === segment ? @klass.escape_fragment(params[segment]) : segment
+ @route.format params
end
def optimize_routes_generation?(t)
@@ -202,7 +183,9 @@ module ActionDispatch
end
def parameterize_args(args)
- @required_parts.zip(args.map(&:to_param))
+ params = {}
+ @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v }
+ params
end
def missing_keys(args)
@@ -225,20 +208,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)
@@ -302,9 +288,7 @@ module ActionDispatch
@finalized = false
@set = Journey::Routes.new
- @router = Journey::Router.new(@set, {
- :parameters_key => PARAMETERS_KEY,
- :request_class => request_class})
+ @router = Journey::Router.new @set
@formatter = Journey::Formatter.new @set
end
@@ -393,6 +377,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
@@ -432,7 +418,9 @@ module ActionDispatch
"http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
end
- path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
+ path = conditions.delete :path_info
+ ast = conditions.delete :parsed_path_info
+ path = build_path(path, ast, requirements, anchor)
conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
@@ -440,8 +428,9 @@ module ActionDispatch
route
end
- def build_path(path, requirements, separators, anchor)
+ def build_path(path, ast, requirements, anchor)
strexp = Journey::Router::Strexp.new(
+ ast,
path,
requirements,
SEPARATORS,
@@ -594,7 +583,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,41 +628,52 @@ 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
- user, password = extract_authentication(options)
- recall = options.delete(:_recall)
+ if options[:user] && options[:password]
+ user = options.delete :user
+ password = options.delete :password
+ end
- original_script_name = options.delete(:original_script_name).presence
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
+ recall = options.delete(:_recall) { {} }
+
+ 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)
- @router.call(env)
+ req = request_class.new(env)
+ req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
+ @router.serve(req)
end
def recognize_path(path, environment = {})
@@ -687,8 +687,8 @@ module ActionDispatch
raise ActionController::RoutingError, e.message
end
- req = @request_class.new(env)
- @router.recognize(req) do |route, _matches, params|
+ req = request_class.new(env)
+ @router.recognize(req) do |route, params|
params.merge!(extras)
params.each do |key, value|
if value.is_a?(String)
@@ -696,14 +696,12 @@ module ActionDispatch
params[key] = URI.parser.unescape(value)
end
end
- old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
- env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
- dispatcher = route.app
- while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
- dispatcher = dispatcher.app
- end
+ old_params = req.path_parameters
+ req.path_parameters = old_params.merge params
+ app = route.app
+ if app.matches?(req) && app.dispatcher?
+ dispatcher = app.app
- if dispatcher.is_a?(Dispatcher)
if dispatcher.controller(params, false)
dispatcher.prepare_params!(params)
return params
@@ -715,17 +713,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/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index cc6b763093..17765d851b 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -3,7 +3,6 @@ require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/try'
require 'rack/test'
-require 'minitest'
module ActionDispatch
module Integration #:nodoc:
@@ -270,12 +269,6 @@ module ActionDispatch
path = location.query ? "#{location.path}?#{location.query}" : location.path
end
- unless ActionController::Base < ActionController::Testing
- ActionController::Base.class_eval do
- include ActionController::Testing
- end
- end
-
hostname, port = host.split(':')
env = {
@@ -300,13 +293,7 @@ module ActionDispatch
# NOTE: rack-test v0.5 doesn't build a default uri correctly
# Make sure requested path is always a full uri
- uri = URI.parse('/')
- uri.scheme ||= env['rack.url_scheme']
- uri.host ||= env['SERVER_NAME']
- uri.port ||= env['SERVER_PORT'].try(:to_i)
- uri += path
-
- session.request(uri.to_s, env)
+ session.request(build_full_uri(path, env), env)
@request_count += 1
@request = ActionDispatch::Request.new(session.last_request.env)
@@ -319,6 +306,10 @@ module ActionDispatch
return response.status
end
+
+ def build_full_uri(path, env)
+ "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
+ end
end
module Runner
@@ -356,7 +347,7 @@ module ActionDispatch
# By default, a single session is automatically created for you, but you
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
- def open_session(app = nil)
+ def open_session
dup.tap do |session|
yield session if block_given?
end
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..6584d20840 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -251,7 +251,6 @@ end
module ActionController
class Base
- include ActionController::Testing
# This stub emulates the Railtie including the URL helpers from a Rails application
include SharedTestRoutes.url_helpers
include SharedTestRoutes.mounted_helpers
@@ -320,8 +319,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 580604df3a..f07d201563 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -56,13 +56,16 @@ class AssertSelectTest < ActionController::TestCase
def setup
super
+ @old_delivery_method = ActionMailer::Base.delivery_method
+ @old_perform_deliveries = ActionMailer::Base.perform_deliveries
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
- ActionMailer::Base.deliveries = []
end
def teardown
super
+ ActionMailer::Base.delivery_method = @old_delivery_method
+ ActionMailer::Base.perform_deliveries = @old_perform_deliveries
ActionMailer::Base.deliveries.clear
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/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index 03d5d27cf4..89667df3a4 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -68,12 +68,11 @@ class ContentTypeTest < ActionController::TestCase
end
def test_render_changed_charset_default
- ActionDispatch::Response.default_charset = "utf-16"
- get :render_defaults
- assert_equal "utf-16", @response.charset
- assert_equal Mime::HTML, @response.content_type
- ensure
- ActionDispatch::Response.default_charset = "utf-8"
+ with_default_charset "utf-16" do
+ get :render_defaults
+ assert_equal "utf-16", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ end
end
# :ported:
@@ -105,12 +104,11 @@ class ContentTypeTest < ActionController::TestCase
end
def test_nil_default_for_erb
- ActionDispatch::Response.default_charset = nil
- get :render_default_for_erb
- assert_equal Mime::HTML, @response.content_type
- assert_nil @response.charset, @response.headers.inspect
- ensure
- ActionDispatch::Response.default_charset = "utf-8"
+ with_default_charset nil do
+ get :render_default_for_erb
+ assert_equal Mime::HTML, @response.content_type
+ assert_nil @response.charset, @response.headers.inspect
+ end
end
def test_default_for_erb
@@ -130,6 +128,16 @@ class ContentTypeTest < ActionController::TestCase
assert_equal Mime::HTML, @response.content_type
assert_equal "utf-8", @response.charset
end
+
+ private
+
+ def with_default_charset(charset)
+ old_default_charset = ActionDispatch::Response.default_charset
+ ActionDispatch::Response.default_charset = charset
+ yield
+ ensure
+ ActionDispatch::Response.default_charset = old_default_charset
+ end
end
class AcceptBasedContentTypeTest < ActionController::TestCase
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index c87494aa64..b2b01b3fa9 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -2,13 +2,13 @@ require 'abstract_unit'
class ActionController::Base
class << self
- %w(append_around_filter prepend_after_filter prepend_around_filter prepend_before_filter skip_after_filter skip_before_filter skip_filter).each do |pending|
+ %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action skip_action_callback).each do |pending|
define_method(pending) do |*args|
$stderr.puts "#{pending} unimplemented: #{args.inspect}"
end unless method_defined?(pending)
end
- def before_filters
+ def before_actions
filters = _process_action_callbacks.select { |c| c.kind == :before }
filters.map! { |c| c.raw_filter }
end
@@ -28,8 +28,8 @@ end
class FilterTest < ActionController::TestCase
class TestController < ActionController::Base
- before_filter :ensure_login
- after_filter :clean_up
+ before_action :ensure_login
+ after_action :clean_up
def show
render :inline => "ran action"
@@ -42,13 +42,13 @@ class FilterTest < ActionController::TestCase
end
def clean_up
- @ran_after_filter ||= []
- @ran_after_filter << "clean_up"
+ @ran_after_action ||= []
+ @ran_after_action << "clean_up"
end
end
class ChangingTheRequirementsController < TestController
- before_filter :ensure_login, :except => [:go_wild]
+ before_action :ensure_login, :except => [:go_wild]
def go_wild
render :text => "gobble"
@@ -56,9 +56,9 @@ class FilterTest < ActionController::TestCase
end
class TestMultipleFiltersController < ActionController::Base
- before_filter :try_1
- before_filter :try_2
- before_filter :try_3
+ before_action :try_1
+ before_action :try_2
+ before_action :try_3
(1..3).each do |i|
define_method "fail_#{i}" do
@@ -78,8 +78,8 @@ class FilterTest < ActionController::TestCase
end
class RenderingController < ActionController::Base
- before_filter :before_filter_rendering
- after_filter :unreached_after_filter
+ before_action :before_action_rendering
+ after_action :unreached_after_action
def show
@ran_action = true
@@ -87,29 +87,29 @@ class FilterTest < ActionController::TestCase
end
private
- def before_filter_rendering
+ def before_action_rendering
@ran_filter ||= []
- @ran_filter << "before_filter_rendering"
+ @ran_filter << "before_action_rendering"
render :inline => "something else"
end
- def unreached_after_filter
- @ran_filter << "unreached_after_filter_after_render"
+ def unreached_after_action
+ @ran_filter << "unreached_after_action_after_render"
end
end
- class RenderingForPrependAfterFilterController < RenderingController
- prepend_after_filter :unreached_prepend_after_filter
+ class RenderingForPrependAfterActionController < RenderingController
+ prepend_after_action :unreached_prepend_after_action
private
- def unreached_prepend_after_filter
- @ran_filter << "unreached_preprend_after_filter_after_render"
+ def unreached_prepend_after_action
+ @ran_filter << "unreached_preprend_after_action_after_render"
end
end
- class BeforeFilterRedirectionController < ActionController::Base
- before_filter :before_filter_redirects
- after_filter :unreached_after_filter
+ class BeforeActionRedirectionController < ActionController::Base
+ before_action :before_action_redirects
+ after_action :unreached_after_action
def show
@ran_action = true
@@ -122,23 +122,23 @@ class FilterTest < ActionController::TestCase
end
private
- def before_filter_redirects
+ def before_action_redirects
@ran_filter ||= []
- @ran_filter << "before_filter_redirects"
+ @ran_filter << "before_action_redirects"
redirect_to(:action => 'target_of_redirection')
end
- def unreached_after_filter
- @ran_filter << "unreached_after_filter_after_redirection"
+ def unreached_after_action
+ @ran_filter << "unreached_after_action_after_redirection"
end
end
- class BeforeFilterRedirectionForPrependAfterFilterController < BeforeFilterRedirectionController
- prepend_after_filter :unreached_prepend_after_filter_after_redirection
+ class BeforeActionRedirectionForPrependAfterActionController < BeforeActionRedirectionController
+ prepend_after_action :unreached_prepend_after_action_after_redirection
private
- def unreached_prepend_after_filter_after_redirection
- @ran_filter << "unreached_prepend_after_filter_after_redirection"
+ def unreached_prepend_after_action_after_redirection
+ @ran_filter << "unreached_prepend_after_action_after_redirection"
end
end
@@ -151,8 +151,8 @@ class FilterTest < ActionController::TestCase
render :inline => "ran action"
end
- def show_without_filter
- render :inline => "ran action without filter"
+ def show_without_action
+ render :inline => "ran action without action"
end
private
@@ -168,70 +168,70 @@ class FilterTest < ActionController::TestCase
end
class ConditionalCollectionFilterController < ConditionalFilterController
- before_filter :ensure_login, :except => [ :show_without_filter, :another_action ]
+ before_action :ensure_login, :except => [ :show_without_action, :another_action ]
end
class OnlyConditionSymController < ConditionalFilterController
- before_filter :ensure_login, :only => :show
+ before_action :ensure_login, :only => :show
end
class ExceptConditionSymController < ConditionalFilterController
- before_filter :ensure_login, :except => :show_without_filter
+ before_action :ensure_login, :except => :show_without_action
end
class BeforeAndAfterConditionController < ConditionalFilterController
- before_filter :ensure_login, :only => :show
- after_filter :clean_up_tmp, :only => :show
+ before_action :ensure_login, :only => :show
+ after_action :clean_up_tmp, :only => :show
end
class OnlyConditionProcController < ConditionalFilterController
- before_filter(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_filter", true) }
+ before_action(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_action", true) }
end
class ExceptConditionProcController < ConditionalFilterController
- before_filter(:except => :show_without_filter) {|c| c.instance_variable_set(:"@ran_proc_filter", true) }
+ before_action(:except => :show_without_action) {|c| c.instance_variable_set(:"@ran_proc_action", true) }
end
class ConditionalClassFilter
- def self.before(controller) controller.instance_variable_set(:"@ran_class_filter", true) end
+ def self.before(controller) controller.instance_variable_set(:"@ran_class_action", true) end
end
class OnlyConditionClassController < ConditionalFilterController
- before_filter ConditionalClassFilter, :only => :show
+ before_action ConditionalClassFilter, :only => :show
end
class ExceptConditionClassController < ConditionalFilterController
- before_filter ConditionalClassFilter, :except => :show_without_filter
+ before_action ConditionalClassFilter, :except => :show_without_action
end
class AnomolousYetValidConditionController < ConditionalFilterController
- before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_filter1", true)}, :except => :show_without_filter) { |c| c.instance_variable_set(:"@ran_proc_filter2", true)}
+ before_action(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_action1", true)}, :except => :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true)}
end
class OnlyConditionalOptionsFilter < ConditionalFilterController
- before_filter :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) }
+ before_action :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) }
end
class ConditionalOptionsFilter < ConditionalFilterController
- before_filter :ensure_login, :if => Proc.new { |c| true }
- before_filter :clean_up_tmp, :if => Proc.new { |c| false }
+ before_action :ensure_login, :if => Proc.new { |c| true }
+ before_action :clean_up_tmp, :if => Proc.new { |c| false }
end
class ConditionalOptionsSkipFilter < ConditionalFilterController
- before_filter :ensure_login
- before_filter :clean_up_tmp
+ before_action :ensure_login
+ before_action :clean_up_tmp
- skip_before_filter :ensure_login, if: -> { false }
- skip_before_filter :clean_up_tmp, if: -> { true }
+ skip_before_action :ensure_login, if: -> { false }
+ skip_before_action :clean_up_tmp, if: -> { true }
end
class ClassController < ConditionalFilterController
- before_filter ConditionalClassFilter
+ before_action ConditionalClassFilter
end
class PrependingController < TestController
- prepend_before_filter :wonderful_life
- # skip_before_filter :fire_flash
+ prepend_before_action :wonderful_life
+ # skip_before_action :fire_flash
private
def wonderful_life
@@ -241,8 +241,8 @@ class FilterTest < ActionController::TestCase
end
class SkippingAndLimitedController < TestController
- skip_before_filter :ensure_login
- before_filter :ensure_login, :only => :index
+ skip_before_action :ensure_login
+ before_action :ensure_login, :only => :index
def index
render :text => 'ok'
@@ -254,9 +254,9 @@ class FilterTest < ActionController::TestCase
end
class SkippingAndReorderingController < TestController
- skip_before_filter :ensure_login
- before_filter :find_record
- before_filter :ensure_login
+ skip_before_action :ensure_login
+ before_action :find_record
+ before_action :ensure_login
def index
render :text => 'ok'
@@ -270,10 +270,10 @@ class FilterTest < ActionController::TestCase
end
class ConditionalSkippingController < TestController
- skip_before_filter :ensure_login, :only => [ :login ]
- skip_after_filter :clean_up, :only => [ :login ]
+ skip_before_action :ensure_login, :only => [ :login ]
+ skip_after_action :clean_up, :only => [ :login ]
- before_filter :find_user, :only => [ :change_password ]
+ before_action :find_user, :only => [ :change_password ]
def login
render :inline => "ran action"
@@ -291,8 +291,8 @@ class FilterTest < ActionController::TestCase
end
class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
- before_filter :conditional_in_parent_before, :only => [:show, :another_action]
- after_filter :conditional_in_parent_after, :only => [:show, :another_action]
+ before_action :conditional_in_parent_before, :only => [:show, :another_action]
+ after_action :conditional_in_parent_after, :only => [:show, :another_action]
private
@@ -308,20 +308,20 @@ class FilterTest < ActionController::TestCase
end
class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
- skip_before_filter :conditional_in_parent_before, :only => :another_action
- skip_after_filter :conditional_in_parent_after, :only => :another_action
+ skip_before_action :conditional_in_parent_before, :only => :another_action
+ skip_after_action :conditional_in_parent_after, :only => :another_action
end
class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
- skip_before_filter :conditional_in_parent_before, :only => :show
+ skip_before_action :conditional_in_parent_before, :only => :show
end
class ProcController < PrependingController
- before_filter(proc { |c| c.instance_variable_set(:"@ran_proc_filter", true) })
+ before_action(proc { |c| c.instance_variable_set(:"@ran_proc_action", true) })
end
class ImplicitProcController < PrependingController
- before_filter { |c| c.instance_variable_set(:"@ran_proc_filter", true) }
+ before_action { |c| c.instance_variable_set(:"@ran_proc_action", true) }
end
class AuditFilter
@@ -367,7 +367,7 @@ class FilterTest < ActionController::TestCase
end
class AuditController < ActionController::Base
- before_filter(AuditFilter)
+ before_action(AuditFilter)
def show
render :text => "hello"
@@ -375,14 +375,14 @@ class FilterTest < ActionController::TestCase
end
class AroundFilterController < PrependingController
- around_filter AroundFilter.new
+ around_action AroundFilter.new
end
class BeforeAfterClassFilterController < PrependingController
begin
filter = AroundFilter.new
- before_filter filter
- after_filter filter
+ before_action filter
+ after_action filter
end
end
@@ -394,18 +394,18 @@ class FilterTest < ActionController::TestCase
super()
end
- before_filter { |c| c.class.execution_log << " before procfilter " }
- prepend_around_filter AroundFilter.new
+ before_action { |c| c.class.execution_log << " before procfilter " }
+ prepend_around_action AroundFilter.new
- after_filter { |c| c.class.execution_log << " after procfilter " }
- append_around_filter AppendedAroundFilter.new
+ after_action { |c| c.class.execution_log << " after procfilter " }
+ append_around_action AppendedAroundFilter.new
end
class MixedSpecializationController < ActionController::Base
class OutOfOrder < StandardError; end
- before_filter :first
- before_filter :second, :only => :foo
+ before_action :first
+ before_action :second, :only => :foo
def foo
render :text => 'foo'
@@ -426,7 +426,7 @@ class FilterTest < ActionController::TestCase
end
class DynamicDispatchController < ActionController::Base
- before_filter :choose
+ before_action :choose
%w(foo bar baz).each do |action|
define_method(action) { render :text => action }
@@ -439,9 +439,9 @@ class FilterTest < ActionController::TestCase
end
class PrependingBeforeAndAfterController < ActionController::Base
- prepend_before_filter :before_all
- prepend_after_filter :after_all
- before_filter :between_before_all_and_after_all
+ prepend_before_action :before_all
+ prepend_after_action :after_all
+ before_action :between_before_all_and_after_all
def before_all
@ran_filter ||= []
@@ -473,7 +473,7 @@ class FilterTest < ActionController::TestCase
end
class RescuedController < ActionController::Base
- around_filter RescuingAroundFilterWithBlock.new
+ around_action RescuingAroundFilterWithBlock.new
def show
raise ErrorToRescue.new("Something made the bad noise.")
@@ -482,10 +482,10 @@ class FilterTest < ActionController::TestCase
class NonYieldingAroundFilterController < ActionController::Base
- before_filter :filter_one
- around_filter :non_yielding_filter
- before_filter :filter_two
- after_filter :filter_three
+ before_action :filter_one
+ around_action :non_yielding_action
+ before_action :action_two
+ after_action :action_three
def index
render :inline => "index"
@@ -498,24 +498,24 @@ class FilterTest < ActionController::TestCase
@filters << "filter_one"
end
- def filter_two
- @filters << "filter_two"
+ def action_two
+ @filters << "action_two"
end
- def non_yielding_filter
+ def non_yielding_action
@filters << "it didn't yield"
@filter_return_value
end
- def filter_three
- @filters << "filter_three"
+ def action_three
+ @filters << "action_three"
end
end
class ImplicitActionsController < ActionController::Base
- before_filter :find_only, :only => :edit
- before_filter :find_except, :except => :edit
+ before_action :find_only, :only => :edit
+ before_action :find_except, :except => :edit
private
@@ -528,7 +528,7 @@ class FilterTest < ActionController::TestCase
end
end
- def test_non_yielding_around_filters_not_returning_false_do_not_raise
+ def test_non_yielding_around_actions_not_returning_false_do_not_raise
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", true
assert_nothing_raised do
@@ -536,7 +536,7 @@ class FilterTest < ActionController::TestCase
end
end
- def test_non_yielding_around_filters_returning_false_do_not_raise
+ def test_non_yielding_around_actions_returning_false_do_not_raise
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", false
assert_nothing_raised do
@@ -544,64 +544,64 @@ class FilterTest < ActionController::TestCase
end
end
- def test_after_filters_are_not_run_if_around_filter_returns_false
+ def test_after_actions_are_not_run_if_around_action_returns_false
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", false
test_process(controller, "index")
assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
end
- def test_after_filters_are_not_run_if_around_filter_does_not_yield
+ def test_after_actions_are_not_run_if_around_action_does_not_yield
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", true
test_process(controller, "index")
assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
end
- def test_added_filter_to_inheritance_graph
- assert_equal [ :ensure_login ], TestController.before_filters
+ def test_added_action_to_inheritance_graph
+ assert_equal [ :ensure_login ], TestController.before_actions
end
def test_base_class_in_isolation
- assert_equal [ ], ActionController::Base.before_filters
+ assert_equal [ ], ActionController::Base.before_actions
end
- def test_prepending_filter
- assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters
+ def test_prepending_action
+ assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_actions
end
- def test_running_filters
+ def test_running_actions
test_process(PrependingController)
assert_equal %w( wonderful_life ensure_login ), assigns["ran_filter"]
end
- def test_running_filters_with_proc
+ def test_running_actions_with_proc
test_process(ProcController)
- assert assigns["ran_proc_filter"]
+ assert assigns["ran_proc_action"]
end
- def test_running_filters_with_implicit_proc
+ def test_running_actions_with_implicit_proc
test_process(ImplicitProcController)
- assert assigns["ran_proc_filter"]
+ assert assigns["ran_proc_action"]
end
- def test_running_filters_with_class
+ def test_running_actions_with_class
test_process(AuditController)
assert assigns["was_audited"]
end
- def test_running_anomolous_yet_valid_condition_filters
+ def test_running_anomolous_yet_valid_condition_actions
test_process(AnomolousYetValidConditionController)
assert_equal %w( ensure_login ), assigns["ran_filter"]
- assert assigns["ran_class_filter"]
- assert assigns["ran_proc_filter1"]
- assert assigns["ran_proc_filter2"]
+ assert assigns["ran_class_action"]
+ assert assigns["ran_proc_action1"]
+ assert assigns["ran_proc_action2"]
- test_process(AnomolousYetValidConditionController, "show_without_filter")
+ test_process(AnomolousYetValidConditionController, "show_without_action")
assert_nil assigns["ran_filter"]
- assert !assigns["ran_class_filter"]
- assert !assigns["ran_proc_filter1"]
- assert !assigns["ran_proc_filter2"]
+ assert !assigns["ran_class_action"]
+ assert !assigns["ran_proc_action1"]
+ assert !assigns["ran_proc_action2"]
end
def test_running_conditional_options
@@ -614,59 +614,59 @@ class FilterTest < ActionController::TestCase
assert_equal %w( ensure_login ), assigns["ran_filter"]
end
- def test_skipping_class_filters
+ def test_skipping_class_actions
test_process(ClassController)
- assert_equal true, assigns["ran_class_filter"]
+ assert_equal true, assigns["ran_class_action"]
skipping_class_controller = Class.new(ClassController) do
- skip_before_filter ConditionalClassFilter
+ skip_before_action ConditionalClassFilter
end
test_process(skipping_class_controller)
- assert_nil assigns['ran_class_filter']
+ assert_nil assigns['ran_class_action']
end
- def test_running_collection_condition_filters
+ def test_running_collection_condition_actions
test_process(ConditionalCollectionFilterController)
assert_equal %w( ensure_login ), assigns["ran_filter"]
- test_process(ConditionalCollectionFilterController, "show_without_filter")
+ test_process(ConditionalCollectionFilterController, "show_without_action")
assert_nil assigns["ran_filter"]
test_process(ConditionalCollectionFilterController, "another_action")
assert_nil assigns["ran_filter"]
end
- def test_running_only_condition_filters
+ def test_running_only_condition_actions
test_process(OnlyConditionSymController)
assert_equal %w( ensure_login ), assigns["ran_filter"]
- test_process(OnlyConditionSymController, "show_without_filter")
+ test_process(OnlyConditionSymController, "show_without_action")
assert_nil assigns["ran_filter"]
test_process(OnlyConditionProcController)
- assert assigns["ran_proc_filter"]
- test_process(OnlyConditionProcController, "show_without_filter")
- assert !assigns["ran_proc_filter"]
+ assert assigns["ran_proc_action"]
+ test_process(OnlyConditionProcController, "show_without_action")
+ assert !assigns["ran_proc_action"]
test_process(OnlyConditionClassController)
- assert assigns["ran_class_filter"]
- test_process(OnlyConditionClassController, "show_without_filter")
- assert !assigns["ran_class_filter"]
+ assert assigns["ran_class_action"]
+ test_process(OnlyConditionClassController, "show_without_action")
+ assert !assigns["ran_class_action"]
end
- def test_running_except_condition_filters
+ def test_running_except_condition_actions
test_process(ExceptConditionSymController)
assert_equal %w( ensure_login ), assigns["ran_filter"]
- test_process(ExceptConditionSymController, "show_without_filter")
+ test_process(ExceptConditionSymController, "show_without_action")
assert_nil assigns["ran_filter"]
test_process(ExceptConditionProcController)
- assert assigns["ran_proc_filter"]
- test_process(ExceptConditionProcController, "show_without_filter")
- assert !assigns["ran_proc_filter"]
+ assert assigns["ran_proc_action"]
+ test_process(ExceptConditionProcController, "show_without_action")
+ assert !assigns["ran_proc_action"]
test_process(ExceptConditionClassController)
- assert assigns["ran_class_filter"]
- test_process(ExceptConditionClassController, "show_without_filter")
- assert !assigns["ran_class_filter"]
+ assert assigns["ran_class_action"]
+ test_process(ExceptConditionClassController, "show_without_action")
+ assert !assigns["ran_class_action"]
end
def test_running_only_condition_and_conditional_options
@@ -674,70 +674,70 @@ class FilterTest < ActionController::TestCase
assert_not assigns["ran_conditional_index_proc"]
end
- def test_running_before_and_after_condition_filters
+ def test_running_before_and_after_condition_actions
test_process(BeforeAndAfterConditionController)
assert_equal %w( ensure_login clean_up_tmp), assigns["ran_filter"]
- test_process(BeforeAndAfterConditionController, "show_without_filter")
+ test_process(BeforeAndAfterConditionController, "show_without_action")
assert_nil assigns["ran_filter"]
end
- def test_around_filter
+ def test_around_action
test_process(AroundFilterController)
assert assigns["before_ran"]
assert assigns["after_ran"]
end
- def test_before_after_class_filter
+ def test_before_after_class_action
test_process(BeforeAfterClassFilterController)
assert assigns["before_ran"]
assert assigns["after_ran"]
end
- def test_having_properties_in_around_filter
+ def test_having_properties_in_around_action
test_process(AroundFilterController)
assert_equal "before and after", assigns["execution_log"]
end
- def test_prepending_and_appending_around_filter
+ def test_prepending_and_appending_around_action
test_process(MixedFilterController)
assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
" after appended aroundfilter after procfilter after aroundfilter ",
MixedFilterController.execution_log
end
- def test_rendering_breaks_filtering_chain
+ def test_rendering_breaks_actioning_chain
response = test_process(RenderingController)
assert_equal "something else", response.body
assert !assigns["ran_action"]
end
- def test_before_filter_rendering_breaks_filtering_chain_for_after_filter
+ def test_before_action_rendering_breaks_actioning_chain_for_after_action
test_process(RenderingController)
- assert_equal %w( before_filter_rendering ), assigns["ran_filter"]
+ assert_equal %w( before_action_rendering ), assigns["ran_filter"]
assert !assigns["ran_action"]
end
- def test_before_filter_redirects_breaks_filtering_chain_for_after_filter
- test_process(BeforeFilterRedirectionController)
+ def test_before_action_redirects_breaks_actioning_chain_for_after_action
+ test_process(BeforeActionRedirectionController)
assert_response :redirect
- assert_equal "http://test.host/filter_test/before_filter_redirection/target_of_redirection", redirect_to_url
- assert_equal %w( before_filter_redirects ), assigns["ran_filter"]
+ assert_equal "http://test.host/filter_test/before_action_redirection/target_of_redirection", redirect_to_url
+ assert_equal %w( before_action_redirects ), assigns["ran_filter"]
end
- def test_before_filter_rendering_breaks_filtering_chain_for_preprend_after_filter
- test_process(RenderingForPrependAfterFilterController)
- assert_equal %w( before_filter_rendering ), assigns["ran_filter"]
+ def test_before_action_rendering_breaks_actioning_chain_for_preprend_after_action
+ test_process(RenderingForPrependAfterActionController)
+ assert_equal %w( before_action_rendering ), assigns["ran_filter"]
assert !assigns["ran_action"]
end
- def test_before_filter_redirects_breaks_filtering_chain_for_preprend_after_filter
- test_process(BeforeFilterRedirectionForPrependAfterFilterController)
+ def test_before_action_redirects_breaks_actioning_chain_for_preprend_after_action
+ test_process(BeforeActionRedirectionForPrependAfterActionController)
assert_response :redirect
- assert_equal "http://test.host/filter_test/before_filter_redirection_for_prepend_after_filter/target_of_redirection", redirect_to_url
- assert_equal %w( before_filter_redirects ), assigns["ran_filter"]
+ assert_equal "http://test.host/filter_test/before_action_redirection_for_prepend_after_action/target_of_redirection", redirect_to_url
+ assert_equal %w( before_action_redirects ), assigns["ran_filter"]
end
- def test_filters_with_mixed_specialization_run_in_order
+ def test_actions_with_mixed_specialization_run_in_order
assert_nothing_raised do
response = test_process(MixedSpecializationController, 'bar')
assert_equal 'bar', response.body
@@ -758,7 +758,7 @@ class FilterTest < ActionController::TestCase
end
end
- def test_running_prepended_before_and_after_filter
+ def test_running_prepended_before_and_after_action
test_process(PrependingBeforeAndAfterController)
assert_equal %w( before_all between_before_all_and_after_all after_all ), assigns["ran_filter"]
end
@@ -775,26 +775,26 @@ class FilterTest < ActionController::TestCase
assert_equal %w( find_record ensure_login ), assigns["ran_filter"]
end
- def test_conditional_skipping_of_filters
+ def test_conditional_skipping_of_actions
test_process(ConditionalSkippingController, "login")
assert_nil assigns["ran_filter"]
test_process(ConditionalSkippingController, "change_password")
assert_equal %w( ensure_login find_user ), assigns["ran_filter"]
test_process(ConditionalSkippingController, "login")
- assert !@controller.instance_variable_defined?("@ran_after_filter")
+ assert !@controller.instance_variable_defined?("@ran_after_action")
test_process(ConditionalSkippingController, "change_password")
- assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_filter")
+ assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_action")
end
- def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional
+ def test_conditional_skipping_of_actions_when_parent_action_is_also_conditional
test_process(ChildOfConditionalParentController)
assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
test_process(ChildOfConditionalParentController, 'another_action')
assert_nil assigns['ran_filter']
end
- def test_condition_skipping_of_filters_when_siblings_also_have_conditions
+ def test_condition_skipping_of_actions_when_siblings_also_have_conditions
test_process(ChildOfConditionalParentController)
assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
test_process(AnotherChildOfConditionalParentController)
@@ -808,7 +808,7 @@ class FilterTest < ActionController::TestCase
assert_nil assigns['ran_filter']
end
- def test_a_rescuing_around_filter
+ def test_a_rescuing_around_action
response = nil
assert_nothing_raised do
response = test_process(RescuedController)
@@ -818,7 +818,7 @@ class FilterTest < ActionController::TestCase
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end
- def test_filters_obey_only_and_except_for_implicit_actions
+ def test_actions_obey_only_and_except_for_implicit_actions
test_process(ImplicitActionsController, 'show')
assert_equal 'Except', assigns(:except)
assert_nil assigns(:only)
@@ -852,7 +852,7 @@ class PostsController < ActionController::Base
include AroundExceptions
end
- module_eval %w(raises_before raises_after raises_both no_raise no_filter).map { |action| "def #{action}; default_action end" }.join("\n")
+ module_eval %w(raises_before raises_after raises_both no_raise no_action).map { |action| "def #{action}; default_action end" }.join("\n")
private
def default_action
@@ -861,9 +861,9 @@ class PostsController < ActionController::Base
end
class ControllerWithSymbolAsFilter < PostsController
- around_filter :raise_before, :only => :raises_before
- around_filter :raise_after, :only => :raises_after
- around_filter :without_exception, :only => :no_raise
+ around_action :raise_before, :only => :raises_before
+ around_action :raise_after, :only => :raises_after
+ around_action :without_exception, :only => :no_raise
private
def raise_before
@@ -895,7 +895,7 @@ class ControllerWithFilterClass < PostsController
end
end
- around_filter YieldingFilter, :only => :raises_after
+ around_action YieldingFilter, :only => :raises_after
end
class ControllerWithFilterInstance < PostsController
@@ -906,11 +906,11 @@ class ControllerWithFilterInstance < PostsController
end
end
- around_filter YieldingFilter.new, :only => :raises_after
+ around_action YieldingFilter.new, :only => :raises_after
end
class ControllerWithProcFilter < PostsController
- around_filter(:only => :no_raise) do |c,b|
+ around_action(:only => :no_raise) do |c,b|
c.instance_variable_set(:"@before", true)
b.call
c.instance_variable_set(:"@after", true)
@@ -918,14 +918,14 @@ class ControllerWithProcFilter < PostsController
end
class ControllerWithNestedFilters < ControllerWithSymbolAsFilter
- around_filter :raise_before, :raise_after, :without_exception, :only => :raises_both
+ around_action :raise_before, :raise_after, :without_exception, :only => :raises_both
end
class ControllerWithAllTypesOfFilters < PostsController
- before_filter :before
- around_filter :around
- after_filter :after
- around_filter :around_again
+ before_action :before
+ around_action :around
+ after_action :after
+ around_action :around_again
private
def before
@@ -951,8 +951,8 @@ class ControllerWithAllTypesOfFilters < PostsController
end
class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
- skip_filter :around_again
- skip_filter :after
+ skip_action_callback :around_again
+ skip_action_callback :after
end
class YieldingAroundFiltersTest < ActionController::TestCase
@@ -963,7 +963,7 @@ class YieldingAroundFiltersTest < ActionController::TestCase
assert_nothing_raised { test_process(controller,'no_raise') }
assert_nothing_raised { test_process(controller,'raises_before') }
assert_nothing_raised { test_process(controller,'raises_after') }
- assert_nothing_raised { test_process(controller,'no_filter') }
+ assert_nothing_raised { test_process(controller,'no_action') }
end
def test_with_symbol
@@ -992,7 +992,7 @@ class YieldingAroundFiltersTest < ActionController::TestCase
assert assigns['after']
end
- def test_nested_filters
+ def test_nested_actions
controller = ControllerWithNestedFilters
assert_nothing_raised do
begin
@@ -1008,31 +1008,31 @@ class YieldingAroundFiltersTest < ActionController::TestCase
end
end
- def test_filter_order_with_all_filter_types
+ def test_action_order_with_all_action_types
test_process(ControllerWithAllTypesOfFilters,'no_raise')
assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)', assigns['ran_filter'].join(' ')
end
- def test_filter_order_with_skip_filter_method
+ def test_action_order_with_skip_action_method
test_process(ControllerWithTwoLessFilters,'no_raise')
assert_equal 'before around (before yield) around (after yield)', assigns['ran_filter'].join(' ')
end
- def test_first_filter_in_multiple_before_filter_chain_halts
+ def test_first_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_1')
assert_equal ' ', response.body
assert_equal 1, controller.instance_variable_get(:@try)
end
- def test_second_filter_in_multiple_before_filter_chain_halts
+ def test_second_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_2')
assert_equal ' ', response.body
assert_equal 2, controller.instance_variable_get(:@try)
end
- def test_last_filter_in_multiple_before_filter_chain_halts
+ def test_last_action_in_multiple_before_action_chain_halts
controller = ::FilterTest::TestMultipleFiltersController.new
response = test_process(controller, 'fail_3')
assert_equal ' ', response.body
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/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index 86b94652ce..ef90fff178 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -132,13 +132,30 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
assert_equal(expected, actual)
end
- private
-
- def sample_request(token)
- @sample_request ||= OpenStruct.new authorization: %{Token token="#{token}"}
+ test "token_and_options returns empty string with empty token" do
+ token = ''
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
end
- def encode_credentials(token, options = {})
- ActionController::HttpAuthentication::Token.encode_credentials(token, options)
+ test "token_and_options returns nil with no value after the equal sign" do
+ actual = ActionController::HttpAuthentication::Token.token_and_options(malformed_request).first
+ expected = nil
+ assert_equal(expected, actual)
end
+
+ private
+
+ def sample_request(token)
+ @sample_request ||= OpenStruct.new authorization: %{Token token="#{token}", nonce="def"}
+ end
+
+ def malformed_request
+ @malformed_request ||= OpenStruct.new authorization: %{Token token=}
+ end
+
+ def encode_credentials(token, options = {})
+ ActionController::HttpAuthentication::Token.encode_credentials(token, options)
+ end
end
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..0500b7c789 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
@@ -186,6 +202,39 @@ module ActionController
response.stream.write ''
response.stream.write params[:widget][:didnt_check_for_nil]
end
+
+ def overfill_buffer_and_die
+ # Write until the buffer is full. It doesn't expose that
+ # information directly, so we must hard-code its size:
+ 10.times do
+ response.stream.write '.'
+ end
+ # .. plus one more, because the #each frees up a slot:
+ response.stream.write '.'
+
+ latch.release
+
+ # This write will block, and eventually raise
+ response.stream.write 'x'
+
+ 20.times do
+ response.stream.write '.'
+ end
+ end
+
+ def ignore_client_disconnect
+ response.stream.ignore_disconnect = true
+
+ response.stream.write '' # commit
+
+ # These writes will be ignored
+ 15.times do
+ response.stream.write 'x'
+ end
+
+ logger.info 'Work complete'
+ latch.release
+ end
end
tests TestController
@@ -248,6 +297,62 @@ module ActionController
assert t.join(3), 'timeout expired before the thread terminated'
end
+ def test_abort_with_full_buffer
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+
+ @request.parameters[:format] = 'plain'
+ @controller.request = @request
+ @controller.response = @response
+
+ got_error = ActiveSupport::Concurrency::Latch.new
+ @response.stream.on_error do
+ ActionController::Base.logger.warn 'Error while streaming'
+ got_error.release
+ end
+
+ t = Thread.new(@response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do |part|
+ @controller.latch.await
+ body.close
+ break
+ end
+ }
+
+ capture_log_output do |output|
+ @controller.process :overfill_buffer_and_die
+ t.join
+ got_error.await
+ assert_match 'Error while streaming', output.rewind && output.read
+ end
+ end
+
+ def test_ignore_client_disconnect
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+
+ @controller.request = @request
+ @controller.response = @response
+
+ t = Thread.new(@response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do |part|
+ body.close
+ break
+ end
+ }
+
+ capture_log_output do |output|
+ @controller.process :ignore_client_disconnect
+ t.join
+ Timeout.timeout(3) do
+ @controller.latch.await
+ end
+ assert_match 'Work complete', output.rewind && output.read
+ end
+ end
+
def test_thread_locals_get_copied
@controller.tc = self
Thread.current[:originating_thread] = Thread.current.object_id
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
index c95ef8a0c7..27871ef351 100644
--- a/actionpack/test/controller/localized_templates_test.rb
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -8,22 +8,24 @@ end
class LocalizedTemplatesTest < ActionController::TestCase
tests LocalizedController
+ setup do
+ @old_locale = I18n.locale
+ end
+
+ teardown do
+ I18n.locale = @old_locale
+ end
+
def test_localized_template_is_used
- old_locale = I18n.locale
I18n.locale = :de
get :hello_world
assert_equal "Gutten Tag", @response.body
- ensure
- I18n.locale = old_locale
end
def test_default_locale_template_is_used_when_locale_is_missing
- old_locale = I18n.locale
I18n.locale = :dk
get :hello_world
assert_equal "Hello World", @response.body
- ensure
- I18n.locale = old_locale
end
def test_use_fallback_locales
@@ -36,13 +38,9 @@ class LocalizedTemplatesTest < ActionController::TestCase
end
def test_localized_template_has_correct_header_with_no_format_in_template_name
- old_locale = I18n.locale
I18n.locale = :it
-
get :hello_world
assert_equal "Ciao Mondo", @response.body
assert_equal "text/html", @response.content_type
- ensure
- I18n.locale = old_locale
end
end
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 ce6d135d92..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"
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/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index 7396c850ad..2ddc07ef72 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -81,8 +81,8 @@ module BareMetalTest
assert_nil headers['Content-Length']
end
- test "head :continue (101) does not return a content-type header" do
- headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ test "head :switching_protocols (101) does not return a content-type header" do
+ headers = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).second
assert_nil headers['Content-Type']
assert_nil headers['Content-Length']
end
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 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/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 33a91d72d9..aa894ffa17 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -167,9 +167,26 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
end
+ # Strong params has an optimization to avoid looping every time you read
+ # a key whose value is an array and building a new object. We check that
+ # optimization here.
test 'arrays are converted at most once' do
params = ActionController::Parameters.new(foo: [{}])
- assert params[:foo].equal?(params[:foo])
+ assert_same params[:foo], params[:foo]
+ end
+
+ # Strong params has an internal cache to avoid duplicated loops in the most
+ # common usage pattern. See the docs of the method `converted_arrays`.
+ #
+ # This test checks that if we push a hash to an array (in-place modification)
+ # the cache does not get fooled, the hash is still wrapped as strong params,
+ # and not permitted.
+ test 'mutated arrays are detected' do
+ params = ActionController::Parameters.new(users: [{id: 1}])
+
+ permitted = params.permit(users: [:id])
+ permitted[:users] << {injected: 1}
+ assert_not permitted[:users].last.permitted?
end
test "fetch doesnt raise ParameterMissing exception if there is a default" do
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 11ccb6cf3b..645ecae220 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -337,14 +337,26 @@ class IrregularInflectionParamsWrapperTest < ActionController::TestCase
tests ParamswrappernewsController
def test_uses_model_attribute_names_with_irregular_inflection
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular 'paramswrappernews_item', 'paramswrappernews'
- end
+ with_dup do
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'paramswrappernews_item', 'paramswrappernews'
+ end
- with_default_wrapper_options do
- @request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' }
- assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }})
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' }
+ assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }})
+ end
end
end
+
+ private
+
+ def with_dup
+ original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
+ yield
+ ensure
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
+ end
end
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
index b5e74e373d..af50e11261 100644
--- a/actionpack/test/controller/render_other_test.rb
+++ b/actionpack/test/controller/render_other_test.rb
@@ -1,9 +1,5 @@
require 'abstract_unit'
-ActionController.add_renderer :simon do |says, options|
- self.content_type = Mime::TEXT
- self.response_body = "Simon says: #{says}"
-end
class RenderOtherTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -15,7 +11,14 @@ class RenderOtherTest < ActionController::TestCase
tests TestController
def test_using_custom_render_option
+ ActionController.add_renderer :simon do |says, options|
+ self.content_type = Mime::TEXT
+ self.response_body = "Simon says: #{says}"
+ end
+
get :render_simon_says
assert_equal "Simon says: foo", @response.body
+ ensure
+ ActionController.remove_renderer :simon
end
end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 5ab5141966..2a5aad9c0e 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -127,11 +127,12 @@ module RequestForgeryProtectionTests
@token = "cf50faa3fe97702ca1ae"
SecureRandom.stubs(:base64).returns(@token)
+ @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token
ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
end
def teardown
- ActionController::Base.request_forgery_protection_token = nil
+ ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token
end
def test_should_render_form_with_token_tag
@@ -376,11 +377,12 @@ class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController
include RequestForgeryProtectionTests
setup do
+ @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token
ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
end
teardown do
- ActionController::Base.request_forgery_protection_token = nil
+ ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token
end
test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
@@ -462,16 +464,38 @@ 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"
+ @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token
+ ActionController::Base.request_forgery_protection_token = @token
end
def teardown
- ActionController::Base.request_forgery_protection_token = :authenticity_token
+ ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token
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/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index 25d0337212..6803dbbb62 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -24,10 +24,26 @@ class ActionControllerRequiredParamsTest < ActionController::TestCase
post :create, { book: { name: "Mjallo!" } }
assert_response :ok
end
+
+ test "required parameters with false value will not raise" do
+ post :create, { book: { name: false } }
+ assert_response :ok
+ end
end
class ParametersRequireTest < ActiveSupport::TestCase
- test "required parameters must be present not merely not nil" do
+
+ test "required parameters should accept and return false value" do
+ assert_equal(false, ActionController::Parameters.new(person: false).require(:person))
+ end
+
+ test "required parameters must not be nil" do
+ assert_raises(ActionController::ParameterMissing) do
+ ActionController::Parameters.new(person: nil).require(:person)
+ end
+ end
+
+ test "required parameters must not be empty" do
assert_raises(ActionController::ParameterMissing) do
ActionController::Parameters.new(person: {}).require(:person)
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index df453a0251..721dad4dd9 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -87,7 +87,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_symbols_with_dashes
rs.draw do
get '/:artist/:song-omg', :to => lambda { |env|
- resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters
[200, {}, [resp]]
}
end
@@ -99,7 +99,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_id_with_dash
rs.draw do
get '/journey/:id', :to => lambda { |env|
- resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters
[200, {}, [resp]]
}
end
@@ -111,7 +111,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_dash_with_custom_regexp
rs.draw do
get '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env|
- resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters
[200, {}, [resp]]
}
end
@@ -124,7 +124,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_pre_dash
rs.draw do
get '/:artist/omg-:song', :to => lambda { |env|
- resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters
[200, {}, [resp]]
}
end
@@ -136,7 +136,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_pre_dash_with_custom_regexp
rs.draw do
get '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env|
- resp = ActiveSupport::JSON.encode env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters
[200, {}, [resp]]
}
end
@@ -243,6 +243,32 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal 'clients', get(URI('http://clients.example.org/'))
end
+ def test_scoped_lambda
+ scope_called = false
+ rs.draw do
+ scope '/foo', :constraints => lambda { |req| scope_called = true } do
+ get '/', :to => lambda { |env| [200, {}, %w{default}] }
+ end
+ end
+
+ assert_equal 'default', get(URI('http://www.example.org/foo/'))
+ assert scope_called, "scope constraint should be called"
+ end
+
+ def test_scoped_lambda_with_get_lambda
+ inner_called = false
+
+ rs.draw do
+ scope '/foo', :constraints => lambda { |req| flunk "should not be called" } do
+ get '/', :constraints => lambda { |req| inner_called = true },
+ :to => lambda { |env| [200, {}, %w{default}] }
+ end
+ end
+
+ assert_equal 'default', get(URI('http://www.example.org/foo/'))
+ assert inner_called, "inner constraint should be called"
+ end
+
def test_empty_string_match
rs.draw do
get '/:username', :constraints => { :username => /[^\/]+/ },
@@ -419,14 +445,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..73ed33ce2d 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -30,11 +30,8 @@ class SendFileWithActionControllerLive < SendFileController
end
class SendFileTest < ActionController::TestCase
- tests SendFileController
include TestFileUtils
- Mime::Type.register "image/png", :png unless defined? Mime::PNG
-
def setup
@controller = SendFileController.new
@request = ActionController::TestRequest.new
@@ -201,8 +198,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/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index fbc10baf21..1141feeff7 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -164,7 +164,7 @@ XML
end
class DefaultUrlOptionsCachingController < ActionController::Base
- before_filter { @dynamic_opt = 'opt' }
+ before_action { @dynamic_opt = 'opt' }
def test_url_options_reset
render text: url_for(params)
@@ -662,7 +662,7 @@ XML
def test_id_converted_to_string
get :test_params, :id => 20, :foo => Object.new
- assert_kind_of String, @request.path_parameters['id']
+ assert_kind_of String, @request.path_parameters[:id]
end
def test_array_path_parameter_handled_properly
@@ -673,17 +673,17 @@ XML
end
get :test_params, :path => ['hello', 'world']
- assert_equal ['hello', 'world'], @request.path_parameters['path']
- assert_equal 'hello/world', @request.path_parameters['path'].to_param
+ assert_equal ['hello', 'world'], @request.path_parameters[:path]
+ assert_equal 'hello/world', @request.path_parameters[:path].to_param
end
end
def test_assert_realistic_path_parameters
get :test_params, :id => 20, :foo => Object.new
- # All elements of path_parameters should use string keys
+ # All elements of path_parameters should use Symbol keys
@request.path_parameters.keys.each do |key|
- assert_kind_of String, key
+ assert_kind_of Symbol, key
end
end
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index a8035e5bd7..7210c68e73 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
@@ -75,7 +95,7 @@ module AbstractController
end
def test_subdomain_may_be_object
- model = mock(:to_param => 'api')
+ model = Class.new { def self.to_param; 'api'; end }
add_host!
assert_equal('http://api.basecamphq.com/c/a/i',
W.new.url_for(:subdomain => model, :controller => 'c', :action => 'a', :id => 'i')
@@ -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/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/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index 58457b0c28..d8d3209dac 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -38,7 +38,7 @@ module ActionDispatch
def test_mapping_requirements
options = { :controller => 'foo', :action => 'bar', :via => :get }
- m = Mapper::Mapping.new FakeSet.new, {}, '/store/:name(*rest)', options
+ m = Mapper::Mapping.build({}, '/store/:name(*rest)', options)
_, _, requirements, _ = m.to_route
assert_equal(/.+?/, requirements[:rest])
end
@@ -72,7 +72,7 @@ module ActionDispatch
mapper = Mapper.new fakeset
mapper.get '/*path/foo/:bar', :to => 'pages#show'
assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
- assert_nil fakeset.requirements.first[:path]
+ assert_equal(/.+?/, fakeset.requirements.first[:path])
end
def test_map_wildcard_with_multiple_wildcard
@@ -80,7 +80,7 @@ module ActionDispatch
mapper = Mapper.new fakeset
mapper.get '/*foo/*bar', :to => 'pages#show'
assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info]
- assert_nil fakeset.requirements.first[:foo]
+ assert_equal(/.+?/, fakeset.requirements.first[:foo])
assert_equal(/.+?/, fakeset.requirements.first[:bar])
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 981cf2426e..d29cc8473e 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -1,8 +1,6 @@
require 'abstract_unit'
class MimeTypeTest < ActiveSupport::TestCase
- Mime::Type.register "image/png", :png unless defined? Mime::PNG
- Mime::Type.register "application/pdf", :pdf unless defined? Mime::PDF
test "parse single" do
Mime::LOOKUP.keys.each do |mime_type|
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index cdf00d84fb..bd3f6274de 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -31,6 +31,8 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
resources :users do
mount FakeEngine, :at => "/fakeengine", :as => :fake_mounted_at_resource
end
+
+ mount SprocketsApp, :at => "/", :via => :get
end
def app
@@ -44,6 +46,11 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
"A named route should be defined with a parent's prefix"
end
+ def test_mounting_at_root_path
+ get "/omg"
+ assert_equal " -- /omg", response.body
+ end
+
def test_mounting_sets_script_name
get "/sprockets/omg"
assert_equal "/sprockets -- /omg", response.body
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/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
index ce9ccfcee8..62e8197e20 100644
--- a/actionpack/test/dispatch/reloader_test.rb
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -3,6 +3,11 @@ require 'abstract_unit'
class ReloaderTest < ActiveSupport::TestCase
Reloader = ActionDispatch::Reloader
+ teardown do
+ Reloader.reset_callbacks :prepare
+ Reloader.reset_callbacks :cleanup
+ end
+
def test_prepare_callbacks
a = b = c = nil
Reloader.to_prepare { |*args| a = b = c = 1 }
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 2a2f92b5b3..2db3fee6bb 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -145,7 +145,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
test "does not raise EOFError on GET request with multipart content-type" do
with_routing do |set|
set.draw do
- get ':action', to: 'multipart_params_parsing_test/test'
+ get ':action', controller: 'multipart_params_parsing_test/test'
end
headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
get "/parse", {}, headers
@@ -174,7 +174,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- post ':action', :to => 'multipart_params_parsing_test/test'
+ post ':action', :controller => 'multipart_params_parsing_test/test'
end
yield
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index d82493140f..4e99c26e03 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -105,6 +105,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
end
test "perform_deep_munge" do
+ old_perform_deep_munge = ActionDispatch::Request::Utils.perform_deep_munge
ActionDispatch::Request::Utils.perform_deep_munge = false
begin
assert_parses({"action" => nil}, "action")
@@ -115,7 +116,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
assert_parses({"action" => {"foo" => [{"bar" => nil}]}}, "action[foo][][bar]")
assert_parses({"action" => ['1',nil]}, "action[]=1&action[]")
ensure
- ActionDispatch::Request::Utils.perform_deep_munge = true
+ ActionDispatch::Request::Utils.perform_deep_munge = old_perform_deep_munge
end
end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 9a77454f30..1de05cbf09 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -130,10 +130,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
end
test "ambiguous params returns a bad request" do
- with_routing do |set|
- set.draw do
- post ':action', to: ::UrlEncodedParamsParsingTest::TestController
- end
+ with_test_routing do
post "/parse", "foo[]=bar&foo[4]=bar"
assert_response :bad_request
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 6e21b4a258..e4950a5d6b 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
@@ -802,6 +805,7 @@ class RequestFormat < BaseRequestTest
end
test "ignore_accept_header" do
+ old_ignore_accept_header = ActionDispatch::Request.ignore_accept_header
ActionDispatch::Request.ignore_accept_header = true
begin
@@ -831,7 +835,7 @@ class RequestFormat < BaseRequestTest
request.expects(:parameters).at_least_once.returns({:format => :json})
assert_equal [ Mime::JSON ], request.formats
ensure
- ActionDispatch::Request.ignore_accept_header = false
+ ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header
end
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 959a3bc5cd..187b9a2420 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -178,6 +178,7 @@ class ResponseTest < ActiveSupport::TestCase
end
test "read x_frame_options, x_content_type_options and x_xss_protection" do
+ original_default_headers = ActionDispatch::Response.default_headers
begin
ActionDispatch::Response.default_headers = {
'X-Frame-Options' => 'DENY',
@@ -193,11 +194,12 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal('nosniff', resp.headers['X-Content-Type-Options'])
assert_equal('1;', resp.headers['X-XSS-Protection'])
ensure
- ActionDispatch::Response.default_headers = nil
+ ActionDispatch::Response.default_headers = original_default_headers
end
end
test "read custom default_header" do
+ original_default_headers = ActionDispatch::Response.default_headers
begin
ActionDispatch::Response.default_headers = {
'X-XX-XXXX' => 'Here is my phone number'
@@ -209,7 +211,7 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal('Here is my phone number', resp.headers['X-XX-XXXX'])
ensure
- ActionDispatch::Response.default_headers = nil
+ ActionDispatch::Response.default_headers = original_default_headers
end
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 f74a0ef945..778dbfc74d 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -99,6 +99,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_namespace_without_controller_segment
+ draw do
+ namespace :admin do
+ get 'hello/:controllers/:action'
+ end
+ end
+ get '/admin/hello/foo/new'
+ assert_equal 'foo', @request.params["controllers"]
+ end
+
def test_session_singleton_resource
draw do
resource :session do
@@ -351,8 +361,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
draw do
controller(:global) do
get 'global/hide_notice'
- get 'global/export', :to => :export, :as => :export_request
- get '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ get 'global/export', :action => :export, :as => :export_request
+ get '/export/:id/:file', :action => :export, :as => :export_download, :constraints => { :file => /.*/ }
get 'global/:action'
end
end
@@ -720,8 +730,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
draw do
resources :replies do
member do
- put :answer, :to => :mark_as_answer
- delete :answer, :to => :unmark_as_answer
+ put :answer, :action => :mark_as_answer
+ delete :answer, :action => :unmark_as_answer
end
end
end
@@ -1178,7 +1188,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
controller :articles do
scope '/articles', :as => 'article' do
scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
- get '/:id', :to => :with_id, :as => ""
+ get '/:id', :action => :with_id, :as => ""
end
end
end
@@ -1425,7 +1435,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_scoped_controller_with_namespace_and_action
draw do
namespace :account do
- get ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
+ get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback
end
end
@@ -1482,7 +1492,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_normalize_namespaced_matches
draw do
namespace :account do
- get 'description', :to => :description, :as => "description"
+ get 'description', :action => :description, :as => "description"
end
end
@@ -1723,7 +1733,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "whatever/:controller(/:action(/:id))"
end
- get 'whatever/foo/bar'
+ get '/whatever/foo/bar'
assert_equal 'foo#bar', @response.body
assert_equal 'http://www.example.com/whatever/foo/bar/1',
@@ -1735,10 +1745,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "whatever/:controller(/:action(/:id))", :id => /\d+/
end
- get 'whatever/foo/bar/show'
+ get '/whatever/foo/bar/show'
assert_equal 'foo/bar#show', @response.body
- get 'whatever/foo/bar/show/1'
+ get '/whatever/foo/bar/show/1'
assert_equal 'foo/bar#show', @response.body
assert_equal 'http://www.example.com/whatever/foo/bar/show',
@@ -2144,7 +2154,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
resources :invoices do
get "outstanding" => "invoices#outstanding", :on => :collection
- get "overdue", :to => :overdue, :on => :collection
+ get "overdue", :action => :overdue, :on => :collection
get "print" => "invoices#print", :as => :print, :on => :member
post "preview" => "invoices#preview", :as => :preview, :on => :new
end
@@ -2232,6 +2242,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
end
+ def test_match_without_via
+ assert_raises(ArgumentError) do
+ draw do
+ match '/foo/bar', :to => 'files#show'
+ end
+ end
+ end
+
+ def test_match_with_empty_via
+ assert_raises(ArgumentError) do
+ draw do
+ match '/foo/bar', :to => 'files#show', :via => []
+ end
+ end
+ end
+
def test_glob_parameter_accepts_regexp
draw do
get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
@@ -2287,12 +2313,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "(/user/:username)/photos" => "photos#index"
end
- get 'user/bob/photos'
+ get '/user/bob/photos'
assert_equal 'photos#index', @response.body
assert_equal 'http://www.example.com/user/bob/photos',
url_for(:controller => "photos", :action => "index", :username => "bob")
- get 'photos'
+ get '/photos'
assert_equal 'photos#index', @response.body
assert_equal 'http://www.example.com/photos',
url_for(:controller => "photos", :action => "index", :username => nil)
@@ -2970,7 +2996,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
assert_raise(ArgumentError) do
- draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
+ assert_deprecated do
+ draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
+ end
end
assert_raise(ArgumentError) do
@@ -3137,6 +3165,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/foo', foo_root_path
end
+ def test_namespace_as_controller
+ draw do
+ namespace :foo do
+ get '/', to: '/bar#index', as: 'root'
+ end
+ end
+
+ get '/foo'
+ assert_equal 'bar#index', @response.body
+ assert_equal '/foo', foo_root_path
+ end
+
def test_trailing_slash
draw do
resources :streams
@@ -3217,6 +3257,58 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/admin/posts/1/comments', admin_post_comments_path('1')
end
+ def test_mix_string_to_controller_action
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_string_to_controller
+ draw do
+ get '/projects', controller: 'project_files',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_string_to_action
+ draw do
+ get '/projects', action: 'index',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_symbol_to_controller_action
+ assert_deprecated do
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: :show
+ end
+ end
+ get '/projects'
+ assert_equal 'project_files#show', @response.body
+ end
+
+ def test_mix_string_to_controller_action_no_hash
+ assert_deprecated do
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: 'show'
+ end
+ end
+ get '/projects'
+ assert_equal 'show#index', @response.body
+ end
+
def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes
draw do
scope shallow_path: 'projects', shallow_prefix: 'project' do
@@ -3368,12 +3460,14 @@ end
class TestAltApp < ActionDispatch::IntegrationTest
class AltRequest
+ attr_accessor :path_parameters, :path_info, :script_name
+ attr_reader :env
+
def initialize(env)
+ @path_parameters = {}
@env = env
- end
-
- def path_info
- "/"
+ @path_info = "/"
+ @script_name = ""
end
def request_method
@@ -3476,6 +3570,35 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
@app.draw(&block)
end
+ def test_missing_controller
+ ex = assert_raises(ArgumentError) {
+ draw do
+ get '/foo/bar', :action => :index
+ end
+ }
+ assert_match(/Missing :controller/, ex.message)
+ end
+
+ def test_missing_action
+ ex = assert_raises(ArgumentError) {
+ assert_deprecated do
+ draw do
+ get '/foo/bar', :to => 'foo'
+ end
+ end
+ }
+ assert_match(/Missing :action/, ex.message)
+ end
+
+ def test_missing_action_on_hash
+ ex = assert_raises(ArgumentError) {
+ draw do
+ get '/foo/bar', :to => 'foo#'
+ end
+ }
+ assert_match(/Missing :action/, ex.message)
+ end
+
def test_valid_controller_options_inside_namespace
draw do
namespace :admin do
@@ -3492,7 +3615,7 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
resources :storage_files, :controller => 'admin/storage_files'
end
- get 'storage_files'
+ get '/storage_files'
assert_equal "admin/storage_files#index", @response.body
end
@@ -3517,6 +3640,16 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
assert_match "'Admin::StorageFiles' is not a supported controller name", e.message
end
+
+ def test_warn_with_ruby_constant_syntax_no_colons
+ e = assert_raise(ArgumentError) do
+ draw do
+ resources :storage_files, :controller => 'Admin'
+ end
+ end
+
+ assert_match "'Admin' is not a supported controller name", e.message
+ end
end
class TestDefaultScope < ActionDispatch::IntegrationTest
@@ -3553,6 +3686,7 @@ class TestHttpMethods < ActionDispatch::IntegrationTest
RFC3648 = %w(ORDERPATCH)
RFC3744 = %w(ACL)
RFC5323 = %w(SEARCH)
+ RFC4791 = %w(MKCALENDAR)
RFC5789 = %w(PATCH)
def simple_app(response)
@@ -3564,13 +3698,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
@@ -3596,8 +3730,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
@@ -3605,7 +3739,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
@@ -3790,6 +3924,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
@@ -3822,6 +3958,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
@@ -3935,7 +4091,7 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
set.draw do
get "/bar/:id", :to => redirect("/foo/show/%{id}")
get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show"
- get "/foo(/:action(/:id))", :to => "test_invalid_urls/foo"
+ get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo"
get "/:controller(/:action(/:id))"
end
@@ -4089,6 +4245,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/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 92544230b2..f7a06cfed4 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -49,6 +49,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal 'foo: "bar"', response.body
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_nil_session_value
@@ -57,6 +59,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal 'foo: nil', response.body
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_session_value_after_session_reset
@@ -76,6 +80,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_from_nonexistent_session
@@ -85,6 +91,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_equal 'foo: nil', response.body
assert_nil cookies['_session_id'], "should only create session on write, not read"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_setting_session_value_after_session_reset
@@ -106,6 +114,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_not_equal session_id, response.body
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_getting_session_id
@@ -119,6 +129,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_deserializes_unloaded_class
@@ -133,6 +145,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
end
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_doesnt_write_session_cookie_if_session_id_is_already_exists
@@ -145,6 +159,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
def test_prevents_session_fixation
@@ -160,6 +176,8 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
assert_response :success
assert_not_equal session_id, cookies['_session_id']
end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
end
rescue LoadError, RuntimeError, Dalli::DalliError
$stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again."
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index fdea27e2d2..a4dfd0a63d 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -15,6 +15,8 @@ module TestUrlGeneration
Routes.draw do
get "/foo", :to => "my_route_generating#index", :as => :foo
+ resources :bars
+
mount MyRouteGeneratingController.action(:index), at: '/bar'
end
@@ -64,18 +66,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
@@ -97,6 +111,22 @@ module TestUrlGeneration
test "omit subdomain when key is blank" do
assert_equal "http://example.com/foo", foo_url(subdomain: "")
end
+
+ test "generating URLs with trailing slashes" do
+ assert_equal "/bars.json", bars_path(
+ trailing_slash: true,
+ format: 'json'
+ )
+ end
+
+ test "generating URLS with querystring and trailing slashes" do
+ assert_equal "/bars.json?a=b", bars_path(
+ trailing_slash: true,
+ a: 'b',
+ format: 'json'
+ )
+ end
+
end
end
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index ce02104181..9dfdfc23ed 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -18,7 +18,7 @@ module ActionDispatch
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar\Z},
}.each do |path, expected|
define_method(:"test_to_regexp_#{path}") do
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
path,
{ :controller => /.+/ },
["/", ".", "?"]
@@ -41,7 +41,7 @@ module ActionDispatch
'/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar},
}.each do |path, expected|
define_method(:"test_to_non_anchored_regexp_#{path}") do
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
path,
{ :controller => /.+/ },
["/", ".", "?"],
@@ -65,7 +65,7 @@ module ActionDispatch
'/:controller/*foo/bar' => %w{ controller foo },
}.each do |path, expected|
define_method(:"test_names_#{path}") do
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
path,
{ :controller => /.+/ },
["/", ".", "?"]
@@ -75,12 +75,8 @@ module ActionDispatch
end
end
- def test_to_raise_exception_with_bad_expression
- assert_raise(ArgumentError, "Bad expression: []") { Pattern.new [] }
- end
-
def test_to_regexp_with_extended_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name',
{ :name => /
#ROFL
@@ -101,13 +97,13 @@ module ActionDispatch
['/:foo(/:bar)', %w{ bar }],
['/:foo(/:bar)/:lol(/:baz)', %w{ bar baz }],
].each do |pattern, list|
- path = Pattern.new pattern
+ path = Pattern.from_string pattern
assert_equal list.sort, path.optional_names.sort
end
end
def test_to_regexp_match_non_optional
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/:name',
{ :name => /\d+/ },
["/", ".", "?"]
@@ -118,7 +114,7 @@ module ActionDispatch
end
def test_to_regexp_with_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name',
{ :name => /(tender|love)/ },
["/", ".", "?"]
@@ -131,7 +127,7 @@ module ActionDispatch
def test_ast_sets_regular_expressions
requirements = { :name => /(tender|love)/, :value => /./ }
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name/:value',
requirements,
["/", ".", "?"]
@@ -148,7 +144,7 @@ module ActionDispatch
end
def test_match_data_with_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name',
{ :name => /(tender|love)/ },
["/", ".", "?"]
@@ -160,7 +156,7 @@ module ActionDispatch
end
def test_match_data_with_multi_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name/:id',
{ :name => /t(((ender|love)))()/ },
["/", ".", "?"]
@@ -175,7 +171,7 @@ module ActionDispatch
def test_star_with_custom_re
z = /\d+/
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/*foo',
{ :foo => z },
["/", ".", "?"]
@@ -185,7 +181,7 @@ module ActionDispatch
end
def test_insensitive_regexp_with_group
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
'/page/:name/aaron',
{ :name => /(tender|love)/i },
["/", ".", "?"]
@@ -197,7 +193,7 @@ module ActionDispatch
end
def test_to_regexp_with_strexp
- strexp = Router::Strexp.new('/:controller', { }, ["/", ".", "?"])
+ strexp = Router::Strexp.build('/:controller', { }, ["/", ".", "?"])
path = Pattern.new strexp
x = %r{\A/([^/.?]+)\Z}
@@ -205,20 +201,20 @@ module ActionDispatch
end
def test_to_regexp_defaults
- path = Pattern.new '/:controller(/:action(/:id))'
+ path = Pattern.from_string '/:controller(/:action(/:id))'
expected = %r{\A/([^/.?]+)(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z}
assert_equal expected, path.to_regexp
end
def test_failed_match
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = 'content'
assert_not path =~ uri
end
def test_match_controller
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = '/content'
match = path =~ uri
@@ -230,7 +226,7 @@ module ActionDispatch
end
def test_match_controller_action
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = '/content/list'
match = path =~ uri
@@ -242,7 +238,7 @@ module ActionDispatch
end
def test_match_controller_action_id
- path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
uri = '/content/list/10'
match = path =~ uri
@@ -254,7 +250,7 @@ module ActionDispatch
end
def test_match_literal
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
uri = '/books'
match = path =~ uri
@@ -264,7 +260,7 @@ module ActionDispatch
end
def test_match_literal_with_action
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
uri = '/books/list'
match = path =~ uri
@@ -274,7 +270,7 @@ module ActionDispatch
end
def test_match_literal_with_action_and_format
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
uri = '/books/list.rss'
match = path =~ uri
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
index cbe6284714..21d867aca0 100644
--- a/actionpack/test/journey/route_test.rb
+++ b/actionpack/test/journey/route_test.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class TestRoute < ActiveSupport::TestCase
def test_initialize
app = Object.new
- path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
route = Route.new("name", app, path, {}, defaults)
@@ -16,7 +16,7 @@ module ActionDispatch
def test_route_adds_itself_as_memo
app = Object.new
- path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
defaults = {}
route = Route.new("name", app, path, {}, defaults)
@@ -26,21 +26,21 @@ module ActionDispatch
end
def test_ip_address
- path = Path::Pattern.new '/messages/:id(.:format)'
+ path = Path::Pattern.from_string '/messages/:id(.:format)'
route = Route.new("name", nil, path, {:ip => '192.168.1.1'},
{ :controller => 'foo', :action => 'bar' })
assert_equal '192.168.1.1', route.ip
end
def test_default_ip
- path = Path::Pattern.new '/messages/:id(.:format)'
+ path = Path::Pattern.from_string '/messages/:id(.:format)'
route = Route.new("name", nil, path, {},
{ :controller => 'foo', :action => 'bar' })
assert_equal(//, route.ip)
end
def test_format_with_star
- path = Path::Pattern.new '/:controller/*extra'
+ path = Path::Pattern.from_string '/:controller/*extra'
route = Route.new("name", nil, path, {},
{ :controller => 'foo', :action => 'bar' })
assert_equal '/foo/himom', route.format({
@@ -50,7 +50,7 @@ module ActionDispatch
end
def test_connects_all_match
- path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
route = Route.new("name", nil, path, {:action => 'bar'}, { :controller => 'foo' })
assert_equal '/foo/bar/10', route.format({
@@ -61,21 +61,21 @@ module ActionDispatch
end
def test_extras_are_not_included_if_optional
- path = Path::Pattern.new '/page/:id(/:action)'
+ path = Path::Pattern.from_string '/page/:id(/:action)'
route = Route.new("name", nil, path, { }, { :action => 'show' })
assert_equal '/page/10', route.format({ :id => 10 })
end
def test_extras_are_not_included_if_optional_with_parameter
- path = Path::Pattern.new '(/sections/:section)/pages/:id'
+ path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
route = Route.new("name", nil, path, { }, { :action => 'show' })
assert_equal '/pages/10', route.format({:id => 10})
end
def test_extras_are_not_included_if_optional_parameter_is_nil
- path = Path::Pattern.new '(/sections/:section)/pages/:id'
+ path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
route = Route.new("name", nil, path, { }, { :action => 'show' })
assert_equal '/pages/10', route.format({:id => 10, :section => nil})
@@ -85,10 +85,10 @@ module ActionDispatch
constraints = {:required_defaults => [:controller, :action]}
defaults = {:controller=>"pages", :action=>"show"}
- path = Path::Pattern.new "/page/:id(/:action)(.:format)"
+ path = Path::Pattern.from_string "/page/:id(/:action)(.:format)"
specific = Route.new "name", nil, path, constraints, defaults
- path = Path::Pattern.new "/:controller(/:action(/:id))(.:format)"
+ path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)"
generic = Route.new "name", nil, path, constraints
knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
diff --git a/actionpack/test/journey/router/strexp_test.rb b/actionpack/test/journey/router/strexp_test.rb
deleted file mode 100644
index 7ccdfb7b4d..0000000000
--- a/actionpack/test/journey/router/strexp_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'abstract_unit'
-
-module ActionDispatch
- module Journey
- class Router
- class TestStrexp < ActiveSupport::TestCase
- def test_many_names
- exp = Strexp.new(
- "/:controller(/:action(/:id(.:format)))",
- {:controller=>/.+?/},
- ["/", ".", "?"],
- true)
-
- assert_equal ["controller", "action", "id", "format"], exp.names
- end
-
- def test_names
- {
- "/bar(.:format)" => %w{ format },
- ":format" => %w{ format },
- ":format-" => %w{ format },
- ":format0" => %w{ format0 },
- ":format1,:format2" => %w{ format1 format2 },
- }.each do |string, expected|
- exp = Strexp.new(string, {}, ["/", ".", "?"])
- assert_equal expected, exp.names
- end
- end
- end
- end
- end
-end
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..2e7e8e1bea 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -4,24 +4,15 @@ require 'abstract_unit'
module ActionDispatch
module Journey
class TestRouter < ActiveSupport::TestCase
- # TODO : clean up routing tests so we don't need this hack
- class StubDispatcher < Routing::RouteSet::Dispatcher; end
-
attr_reader :routes
def setup
- @app = StubDispatcher.new
+ @app = Routing::RouteSet::Dispatcher.new({})
@routes = Routes.new
- @router = Router.new(@routes, {})
+ @router = Router.new(@routes)
@formatter = Formatter.new(@routes)
end
- def test_request_class_reader
- klass = Object.new
- router = Router.new(routes, :request_class => klass)
- assert_equal klass, router.request_class
- end
-
class FakeRequestFeeler < Struct.new(:env, :called)
def new env
self.env = env
@@ -39,33 +30,33 @@ module ActionDispatch
end
def test_dashes
- router = Router.new(routes, {})
+ router = Router.new(routes)
- exp = Router::Strexp.new '/foo-bar-baz', {}, ['/.?']
+ exp = Router::Strexp.build '/foo-bar-baz', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, {}, {:id => nil}, {}
env = rails_env 'PATH_INFO' => '/foo-bar-baz'
called = false
- router.recognize(env) do |r, _, params|
+ router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_unicode
- router = Router.new(routes, {})
+ router = Router.new(routes)
#match the escaped version of /ほげ
- exp = Router::Strexp.new '/%E3%81%BB%E3%81%92', {}, ['/.?']
+ exp = Router::Strexp.build '/%E3%81%BB%E3%81%92', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, {}, {:id => nil}, {}
env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
called = false
- router.recognize(env) do |r, _, params|
+ router.recognize(env) do |r, params|
called = true
end
assert called
@@ -73,17 +64,17 @@ module ActionDispatch
def test_request_class_and_requirements_success
klass = FakeRequestFeeler.new nil
- router = Router.new(routes, {:request_class => klass })
+ router = Router.new(routes)
requirements = { :hello => /world/ }
- exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, requirements, {:id => nil}, {}
- env = rails_env 'PATH_INFO' => '/foo/10'
- router.recognize(env) do |r, _, params|
+ env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
+ router.recognize(env) do |r, params|
assert_equal({:id => '10'}, params)
end
@@ -93,17 +84,17 @@ module ActionDispatch
def test_request_class_and_requirements_fail
klass = FakeRequestFeeler.new nil
- router = Router.new(routes, {:request_class => klass })
+ router = Router.new(routes)
requirements = { :hello => /mom/ }
- exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
router.routes.add_route nil, path, requirements, {:id => nil}, {}
- env = rails_env 'PATH_INFO' => '/foo/10'
- router.recognize(env) do |r, _, params|
+ env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
+ router.recognize(env) do |r, params|
flunk 'route should not be found'
end
@@ -111,24 +102,29 @@ module ActionDispatch
assert_equal env.env, klass.env
end
- class CustomPathRequest < Router::NullReq
+ class CustomPathRequest < ActionDispatch::Request
def path_info
env['custom.path_info']
end
+
+ def path_info=(x)
+ env['custom.path_info'] = x
+ end
end
def test_request_class_overrides_path_info
- router = Router.new(routes, {:request_class => CustomPathRequest })
+ router = Router.new(routes)
- exp = Router::Strexp.new '/bar', {}, ['/.?']
+ exp = Router::Strexp.build '/bar', {}, ['/.?']
path = Path::Pattern.new exp
routes.add_route nil, path, {}, {}, {}
- env = rails_env 'PATH_INFO' => '/foo', 'custom.path_info' => '/bar'
+ env = rails_env({'PATH_INFO' => '/foo',
+ 'custom.path_info' => '/bar'}, CustomPathRequest)
recognized = false
- router.recognize(env) do |r, _, params|
+ router.recognize(env) do |r, params|
recognized = true
end
@@ -137,14 +133,14 @@ module ActionDispatch
def test_regexp_first_precedence
add_routes @router, [
- Router::Strexp.new("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
- Router::Strexp.new("/whois/:id(.:format)", {}, ['/', '.', '?'])
+ Router::Strexp.build("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
+ Router::Strexp.build("/whois/:id(.:format)", {}, ['/', '.', '?'])
]
env = rails_env 'PATH_INFO' => '/whois/example.com'
list = []
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
list << r
end
assert_equal 2, list.length
@@ -156,50 +152,50 @@ module ActionDispatch
def test_required_parts_verified_are_anchored
add_routes @router, [
- Router::Strexp.new("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
+ Router::Strexp.build("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
]
assert_raises(ActionController::UrlGenerationError) do
- @formatter.generate(:path_info, nil, { :id => '10' }, { })
+ @formatter.generate(nil, { :id => '10' }, { })
end
end
def test_required_parts_are_verified_when_building
add_routes @router, [
- Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ Router::Strexp.build("/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
def test_only_required_parts_are_verified
add_routes @router, [
- Router::Strexp.new("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
+ Router::Strexp.build("/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
def test_knows_what_parts_are_missing_from_named_route
route_name = "gorby_thunderhorse"
- pattern = Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ pattern = Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
path = Path::Pattern.new pattern
@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)
@@ -207,42 +203,43 @@ module ActionDispatch
def test_X_Cascade
add_routes @router, [ "/messages(.:format)" ]
- resp = @router.call({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' })
+ resp = @router.serve(rails_env({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' }))
assert_equal ['Not Found'], resp.last
assert_equal 'pass', resp[1]['X-Cascade']
assert_equal 404, resp.first
end
def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
- strexp = Router::Strexp.new("/", {}, ['/', '.', '?'], false)
- path = Path::Pattern.new strexp
+ route_set = Routing::RouteSet.new
+ mapper = Routing::Mapper.new route_set
+
app = lambda { |env| [200, {}, ['success!']] }
- @router.routes.add_route(app, path, {}, {}, {})
+ mapper.get '/weblog', :to => app
env = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog')
- resp = @router.call(env)
+ resp = route_set.call env
assert_equal ['success!'], resp.last
assert_equal '', env['SCRIPT_NAME']
end
def test_defaults_merge_correctly
- path = Path::Pattern.new '/foo(/:id)'
+ path = Path::Pattern.from_string '/foo(/:id)'
@router.routes.add_route nil, path, {}, {:id => nil}, {}
env = rails_env 'PATH_INFO' => '/foo/10'
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
assert_equal({:id => '10'}, params)
end
env = rails_env 'PATH_INFO' => '/foo'
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
assert_equal({:id => nil}, params)
end
end
def test_recognize_with_unbound_regexp
add_routes @router, [
- Router::Strexp.new("/foo", { }, ['/', '.', '?'], false)
+ Router::Strexp.build("/foo", { }, ['/', '.', '?'], false)
]
env = rails_env 'PATH_INFO' => '/foo/bar'
@@ -255,7 +252,7 @@ module ActionDispatch
def test_bound_regexp_keeps_path_info
add_routes @router, [
- Router::Strexp.new("/foo", { }, ['/', '.', '?'], true)
+ Router::Strexp.build("/foo", { }, ['/', '.', '?'], true)
]
env = rails_env 'PATH_INFO' => '/foo'
@@ -287,14 +284,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,35 +301,35 @@ 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
def test_nil_path_parts_are_ignored
- path = Path::Pattern.new "/:controller(/:action(.:format))"
+ path = Path::Pattern.from_string "/:controller(/:action(.:format))"
@router.routes.add_route @app, path, {}, {}, {}
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
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
- str = Router::Strexp.new("/", Hash[params], ['/', '.', '?'], true)
+ str = Router::Strexp.build("/", Hash[params], ['/', '.', '?'], true)
path = Path::Pattern.new str
@router.routes.add_route @app, path, {}, {}, {}
- path, _ = @formatter.generate(:path_info, nil, Hash[params], {})
+ path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
end
def test_generate_calls_param_proc
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@router.routes.add_route @app, path, {}, {}, {}
parameterized = []
@@ -340,7 +337,6 @@ module ActionDispatch
[:action, "show"] ]
@formatter.generate(
- :path_info,
nil,
Hash[params],
{},
@@ -350,31 +346,42 @@ module ActionDispatch
end
def test_generate_id
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/:controller(/:action)'
@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
def test_generate_escapes
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/: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.from_string '/: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)'
+ path = Path::Pattern.from_string '/: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",
@@ -385,10 +392,10 @@ module ActionDispatch
end
def test_generate_uses_recall_if_needed
- path = Path::Pattern.new '/:controller(/:action(/:id))'
+ path = Path::Pattern.from_string '/: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"})
@@ -397,10 +404,10 @@ module ActionDispatch
end
def test_generate_with_name
- path = Path::Pattern.new '/:controller(/:action)'
+ path = Path::Pattern.from_string '/: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"})
@@ -414,14 +421,14 @@ module ActionDispatch
'/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
}.each do |request_path, expected|
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
- path = Path::Pattern.new "/:controller(/:action(/:id))"
+ path = Path::Pattern.from_string "/:controller(/:action(/:id))"
app = Object.new
route = @router.routes.add_route(app, path, {}, {}, {})
env = rails_env 'PATH_INFO' => request_path
called = false
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -436,14 +443,14 @@ module ActionDispatch
:splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
}.each do |name, (request_path, expected)|
define_method("test_recognize_#{name}") do
- path = Path::Pattern.new '/:segment/*splat'
+ path = Path::Pattern.from_string '/:segment/*splat'
app = Object.new
route = @router.routes.add_route(app, path, {}, {}, {})
env = rails_env 'PATH_INFO' => request_path
called = false
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -454,7 +461,7 @@ module ActionDispatch
end
def test_namespaced_controller
- strexp = Router::Strexp.new(
+ strexp = Router::Strexp.build(
"/:controller(/:action(/:id))",
{ :controller => /.+?/ },
["/", ".", "?"]
@@ -471,7 +478,7 @@ module ActionDispatch
:id => '10'
}
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -480,14 +487,14 @@ module ActionDispatch
end
def test_recognize_literal
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
route = @router.routes.add_route(app, path, {}, {:controller => 'books'})
env = rails_env 'PATH_INFO' => '/books/list.rss'
expected = { :controller => 'books', :action => 'list', :format => 'rss' }
called = false
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
@@ -497,7 +504,7 @@ module ActionDispatch
end
def test_recognize_head_request_as_get_route
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
conditions = {
:request_method => 'GET'
@@ -508,7 +515,7 @@ module ActionDispatch
"REQUEST_METHOD" => "HEAD"
called = false
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
called = true
end
@@ -516,7 +523,7 @@ module ActionDispatch
end
def test_recognize_cares_about_verbs
- path = Path::Pattern.new "/books(/:action(.:format))"
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
app = Object.new
conditions = {
:request_method => 'GET'
@@ -532,7 +539,7 @@ module ActionDispatch
"REQUEST_METHOD" => "POST"
called = false
- @router.recognize(env) do |r, _, params|
+ @router.recognize(env) do |r, params|
assert_equal post, r
called = true
end
@@ -544,15 +551,17 @@ module ActionDispatch
def add_routes router, paths
paths.each do |path|
- path = Path::Pattern.new path
+ if String === path
+ path = Path::Pattern.from_string path
+ else
+ path = Path::Pattern.new path
+ end
router.routes.add_route @app, path, {}, {}, {}
end
end
- RailsEnv = Struct.new(:env)
-
- def rails_env env
- RailsEnv.new rack_env env
+ def rails_env env, klass = ActionDispatch::Request
+ klass.new env
end
def rack_env env
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index 25e0321d31..a4efc82b8c 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class TestRoutes < ActiveSupport::TestCase
def test_clear
routes = Routes.new
- exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
path = Path::Pattern.new exp
requirements = { :hello => /world/ }
@@ -18,7 +18,7 @@ module ActionDispatch
def test_ast
routes = Routes.new
- path = Path::Pattern.new '/hello'
+ path = Path::Pattern.from_string '/hello'
routes.add_route nil, path, {}, {}, {}
ast = routes.ast
@@ -28,7 +28,7 @@ module ActionDispatch
def test_simulator_changes
routes = Routes.new
- path = Path::Pattern.new '/hello'
+ path = Path::Pattern.from_string '/hello'
routes.add_route nil, path, {}, {}, {}
sim = routes.simulator
@@ -40,8 +40,8 @@ module ActionDispatch
#def add_route app, path, conditions, defaults, name = nil
routes = Routes.new
- one = Path::Pattern.new '/hello'
- two = Path::Pattern.new '/aaron'
+ one = Path::Pattern.from_string '/hello'
+ two = Path::Pattern.from_string '/aaron'
routes.add_route nil, one, {}, {}, 'aaron'
routes.add_route nil, two, {}, {}, 'aaron'
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 8578b43d78..d825d3b627 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,81 @@
+* Flatten the array parameter in `safe_join`, so it behaves consistently with
+ `Array#join`.
+
+ *Paul Grayson*
+
+* Honor `html_safe` on array elements in tag values, as we do for plain string
+ values.
+
+ *Paul Grayson*
+
+* Add `ActionView::Template::Handler.unregister_template_handler`.
+
+ It performs the opposite of `ActionView::Template::Handler.register_template_handler`.
+
+ *Zuhao Wan*
+
+* Bring `cache_digest` rake tasks up-to-date with the latest API changes
+
+ *Jiri Pospisil*
+
+* 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
@@ -42,5 +120,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/README.rdoc b/actionview/README.rdoc
index 35f805346c..5bb62c7562 100644
--- a/actionview/README.rdoc
+++ b/actionview/README.rdoc
@@ -29,6 +29,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/actionview/RUNNING_UNIT_TESTS.rdoc b/actionview/RUNNING_UNIT_TESTS.rdoc
index 104b3e288d..c408882827 100644
--- a/actionview/RUNNING_UNIT_TESTS.rdoc
+++ b/actionview/RUNNING_UNIT_TESTS.rdoc
@@ -4,7 +4,7 @@ The easiest way to run the unit tests is through Rake. The default task runs
the entire test suite for all classes. For more information, checkout the
full array of rake tasks with "rake -T"
-Rake can be found at http://rake.rubyforge.org
+Rake can be found at http://docs.seattlerb.org/rake/.
== Running by hand
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 455ce531ae..900f96255e 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -131,7 +131,8 @@ module ActionView #:nodoc:
# end
# end
#
- # More builder documentation can be found at http://builder.rubyforge.org.
+ # For more information on Builder please consult the [source
+ # code](https://github.com/jimweirich/builder).
class Base
include Helpers, ::ERB::Util, Context
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index aa49f1edc1..7333ea999a 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
@@ -251,19 +258,19 @@ module ActionView
# ==== Examples
#
# video_tag("trailer")
- # # => <video src="/videos/trailer" />
+ # # => <video src="/videos/trailer"></video>
# video_tag("trailer.ogg")
- # # => <video src="/videos/trailer.ogg" />
+ # # => <video src="/videos/trailer.ogg"></video>
# video_tag("trailer.ogg", controls: true, autobuffer: true)
- # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
+ # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
- # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
# video_tag("/trailers/hd.avi", size: "16x16")
- # # => <video src="/trailers/hd.avi" width="16" height="16" />
+ # # => <video src="/trailers/hd.avi" width="16" height="16"></video>
# video_tag("/trailers/hd.avi", size: "16")
- # # => <video height="16" src="/trailers/hd.avi" width="16" />
+ # # => <video height="16" src="/trailers/hd.avi" width="16"></video>
# video_tag("/trailers/hd.avi", height: '32', width: '32')
- # # => <video height="32" src="/trailers/hd.avi" width="32" />
+ # # => <video height="32" src="/trailers/hd.avi" width="32"></video>
# video_tag("trailer.ogg", "trailer.flv")
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"])
@@ -282,11 +289,11 @@ module ActionView
# your public audios directory.
#
# audio_tag("sound")
- # # => <audio src="/audios/sound" />
+ # # => <audio src="/audios/sound"></audio>
# audio_tag("sound.wav")
- # # => <audio src="/audios/sound.wav" />
+ # # => <audio src="/audios/sound.wav"></audio>
# audio_tag("sound.wav", autoplay: true, controls: true)
- # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
+ # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
# audio_tag("sound.wav", "sound.mid")
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
def audio_tag(*sources)
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index c830ab23e3..006b15be91 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -7,10 +7,10 @@ module ActionView
# urls.
#
# image_path("rails.png")
- # # => "/assets/rails.png"
+ # # => "/images/rails.png"
#
# image_url("rails.png")
- # # => "http://www.example.com/assets/rails.png"
+ # # => "http://www.example.com/images/rails.png"
#
# === Using asset hosts
#
@@ -88,9 +88,12 @@ module ActionView
# still sending assets for plain HTTP requests from asset hosts. If you don't
# have SSL certificates for each of the asset hosts this technique allows you
# to avoid warnings in the client about mixed media.
+ # Note that the request parameter might not be supplied, e.g. when the assets
+ # are precompiled via a Rake task. Make sure to use a Proc instead of a lambda,
+ # since a Proc allows missing parameters and sets them to nil.
#
# config.action_controller.asset_host = Proc.new { |source, request|
- # if request.ssl?
+ # if request && request.ssl?
# "#{request.protocol}#{request.host_with_port}"
# else
# "#{request.protocol}assets.example.com"
@@ -113,13 +116,13 @@ module ActionView
#
# All other asset *_path helpers delegate through this method.
#
- # asset_path "application.js" # => /application.js
- # asset_path "application", type: :javascript # => /javascripts/application.js
- # asset_path "application", type: :stylesheet # => /stylesheets/application.css
+ # asset_path "application.js" # => /assets/application.js
+ # asset_path "application", type: :javascript # => /assets/application.js
+ # asset_path "application", type: :stylesheet # => /assets/application.css
# asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def asset_path(source, options = {})
- source = source.to_s
return "" unless source.present?
+ source = source.to_s
return source if source =~ URI_REGEXP
tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
@@ -134,11 +137,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 +150,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/assets/application.js
+ #
def asset_url(source, options = {})
path_to_asset(source, options.merge(:protocol => :request))
end
@@ -191,7 +201,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)
@@ -223,7 +234,7 @@ module ActionView
# Computes the path to a javascript asset in the public javascripts directory.
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
# Full paths from the document root will be passed through.
- # Used internally by javascript_include_tag to build the script path.
+ # Used internally by +javascript_include_tag+ to build the script path.
#
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
@@ -243,7 +254,7 @@ module ActionView
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
# Computes the path to a stylesheet asset in the public stylesheets directory.
- # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
+ # If the +source+ filename has no extension, .css will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
@@ -268,9 +279,9 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +image_tag+ to build the image path:
#
- # image_path("edit") # => "/assets/edit"
- # image_path("edit.png") # => "/assets/edit.png"
- # image_path("icons/edit.png") # => "/assets/icons/edit.png"
+ # image_path("edit") # => "/images/edit"
+ # image_path("edit.png") # => "/images/edit.png"
+ # image_path("icons/edit.png") # => "/images/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
@@ -334,9 +345,9 @@ module ActionView
# Computes the path to a font asset.
# Full paths from the document root will be passed through.
#
- # font_path("font") # => /assets/font
- # font_path("font.ttf") # => /assets/font.ttf
- # font_path("dir/font.ttf") # => /assets/dir/font.ttf
+ # font_path("font") # => /fonts/font
+ # font_path("font.ttf") # => /fonts/font.ttf
+ # font_path("dir/font.ttf") # => /fonts/dir/font.ttf
# font_path("/dir/font.ttf") # => /dir/font.ttf
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
def font_path(source, options = {})
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index 5afe435459..75d1634b2e 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -202,15 +202,6 @@ module ActionView
ensure
self.output_buffer = old_buffer
end
-
- # Add the output buffer to the response body and start a new one.
- def flush_output_buffer #:nodoc:
- if output_buffer && !output_buffer.empty?
- response.stream.write output_buffer
- self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
- nil
- end
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index c29c1b1eea..16cddec339 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -11,20 +11,16 @@ module ActionView
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
# Useful for inspecting an object at the time of rendering.
#
- # @user = User.new({ username: 'testing', password: 'xyz', age: 42}) %>
+ # @user = User.new({ username: 'testing', password: 'xyz', age: 42})
# debug(@user)
# # =>
# <pre class='debug_dump'>--- !ruby/object:User
# attributes:
# &nbsp; updated_at:
# &nbsp; username: testing
- #
# &nbsp; age: 42
# &nbsp; password: xyz
# &nbsp; created_at:
- # attributes_cache: {}
- #
- # new_record: true
# </pre>
def debug(object)
Marshal::dump(object)
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 1ff090f244..789a413c8d 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -434,7 +434,8 @@ module ActionView
output = capture(builder, &block)
html_options[:multipart] ||= builder.multipart?
- form_tag(options[:url] || {}, html_options) { output }
+ html_options = html_options_for_form(options[:url] || {}, html_options)
+ form_tag_with_body(html_options, output)
end
def apply_form_for_options!(record, object, options) #:nodoc:
@@ -449,7 +450,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 +751,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 66c9e20682..9c0c43d096 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -67,7 +67,7 @@ module ActionView
def form_tag(url_for_options = {}, options = {}, &block)
html_options = html_options_for_form(url_for_options, options)
if block_given?
- form_tag_in_block(html_options, &block)
+ form_tag_with_body(html_options, capture(&block))
else
form_tag_html(html_options)
end
@@ -82,14 +82,18 @@ module ActionView
# ==== Options
# * <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>:include_blank</tt> - If set to true, an empty option will be created. If set to a string, the string will be used as the option's content and the value will be empty.
+ # * <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>
#
@@ -105,13 +109,16 @@ module ActionView
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
# # <option>Out</option></select>
#
- # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input'
- # # => <select class="form_input" id="access" multiple="multiple" name="access[]"><option>Read</option>
+ # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input', id: 'unique_id'
+ # # => <select class="form_input" id="unique_id" multiple="multiple" name="access[]"><option>Read</option>
# # <option>Write</option></select>
#
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true
# # => <select id="people" name="people"><option value=""></option><option value="1">David</option></select>
#
+ # select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: "All"
+ # # => <select id="people" name="people"><option value="">All</option><option value="1">David</option></select>
+ #
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something"
# # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
#
@@ -614,6 +621,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
@@ -698,6 +718,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
@@ -709,12 +742,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"
@@ -735,7 +796,10 @@ module ActionView
# Creates the hidden UTF8 enforcer tag. Override this method in a helper
# to customize the tag.
def utf8_enforcer_tag
- tag(:input, :type => "hidden", :name => "utf8", :value => "&#x2713;".html_safe)
+ # Use raw HTML to ensure the value is written as an HTML entity; it
+ # needs to be the right character regardless of which encoding the
+ # browser infers.
+ '<input name="utf8" type="hidden" value="&#x2713;" />'.html_safe
end
private
@@ -790,8 +854,7 @@ module ActionView
tag(:form, html_options, true) + extra_tags
end
- def form_tag_in_block(html_options, &block)
- content = capture(&block)
+ def form_tag_with_body(html_options, content)
output = form_tag_html(html_options)
output << content
output.safe_concat("</form>")
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index e475d5b018..629c447f3f 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -47,7 +47,13 @@ module ActionView
# tag.
#
# javascript_tag "alert('All is good')", defer: 'defer'
- # # => <script defer="defer">alert('All is good')</script>
+ #
+ # Returns:
+ # <script defer="defer">
+ # //<![CDATA[
+ # alert('All is good')
+ # //]]>
+ # </script>
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 7157a95146..7220bded3c 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -266,14 +266,8 @@ module ActionView
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
- #
- # Non-significant zeros after the fractional separator are
- # stripped out by default (set
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change
- # that):
- #
- # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
- # number_to_human_size(524288000, precision: 5) # => "500 MB"
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
delegate_number_helper_method(:number_to_human_size, number, options)
end
@@ -343,11 +337,15 @@ module ActionView
# separator: ',',
# significant: false) # => "1,2 Million"
#
+ # number_to_human(500000000, precision: 5) # => "500 Million"
+ # number_to_human(12345012345, significant: false) # => "12.345 Billion"
+ #
# Non-significant zeros after the decimal separator are stripped
# out by default (set <tt>:strip_insignificant_zeros</tt> to
# +false+ to change that):
- # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
- # number_to_human(500000000, precision: 5) # => "500 Million"
+ #
+ # number_to_human(12.00001) # => "12"
+ # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
#
# ==== Custom Unit Quantifiers
#
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index 60a4478c26..b0d9c7c7f9 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -18,9 +18,9 @@ module ActionView #:nodoc:
end
# This method returns a html safe string similar to what <tt>Array#join</tt>
- # would return. All items in the array, including the supplied separator, are
- # html escaped unless they are html safe, and the returned string is marked
- # as html safe.
+ # would return. The array is flattened, and all items, including
+ # the supplied separator, are html escaped unless they are html
+ # safe, and the returned string is marked as html safe.
#
# safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
# # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
@@ -29,9 +29,9 @@ module ActionView #:nodoc:
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep=$,)
- sep = ERB::Util.html_escape(sep)
+ sep = ERB::Util.unwrapped_html_escape(sep)
- array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
+ array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
end
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index e5cb843670..049af275b6 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -48,7 +48,7 @@ module ActionView
# Change allowed default attributes
#
# class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
+ # config.action_view.sanitized_allowed_attributes = ['id', 'class', 'style']
# end
#
# Please note that sanitizing user-provided text does not guarantee that the
@@ -204,7 +204,7 @@ module ActionView
# Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
#
# class Application < Rails::Application
- # config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
+ # config.action_view.sanitized_allowed_attributes = ['onclick', 'longdesc']
# end
#
def sanitized_allowed_attributes=(attributes)
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a9f3b0ffbc..35444bcfb4 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -139,7 +139,7 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- content = ERB::Util.h(content) if escape
+ content = ERB::Util.unwrapped_html_escape(content) if escape
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
end
@@ -173,8 +173,11 @@ module ActionView
end
def tag_option(key, value, escape)
- value = value.join(" ") if value.is_a?(Array)
- value = ERB::Util.h(value) if escape
+ if value.is_a?(Array)
+ value = escape ? safe_join(value, " ") : value.join(" ")
+ else
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value
+ end
%(#{key}="#{value}")
end
end
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/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index e910879ebf..e0b80d81c2 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -7,7 +7,6 @@ module ActionView
options["size"] = options["maxlength"] unless options.key?("size")
options["type"] ||= field_type
options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
- options["value"] &&= ERB::Util.html_escape(options["value"])
add_default_name_and_id(options)
tag("input", options)
end
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 894616a449..c3be47133c 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -462,8 +462,6 @@ module ActionView
# <strong>Email me:</strong> <span>me@domain.com</span>
# </a>
def mail_to(email_address, name = nil, html_options = {}, &block)
- email_address = ERB::Util.html_escape(email_address)
-
html_options, name = name, nil if block_given?
html_options = (html_options || {}).stringify_keys
@@ -471,11 +469,11 @@ module ActionView
option = html_options.delete(item) || next
"#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
- extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
+ extras = extras.empty? ? '' : '?' + extras.join('&')
- html_options["href"] = "mailto:#{email_address}#{extras}".html_safe
+ html_options["href"] = "mailto:#{email_address}#{extras}"
- content_tag(:a, name || email_address.html_safe, html_options, &block)
+ content_tag(:a, name || email_address, html_options, &block)
end
# True if the current request URI was generated by the given +options+.
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/rendering.rb b/actionview/lib/action_view/rendering.rb
index 017302d40f..c92d090cce 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -62,8 +62,8 @@ module ActionView
#
# The view class must have the following methods:
# View.new[lookup_context, assigns, controller]
- # Create a new ActionView instance for a controller
- # View#render[options]
+ # Create a new ActionView instance for a controller and we can also pass the arguments.
+ # View#render(option)
# Returns String with the rendered template
#
# Override this method in a module to change the default behavior.
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/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake
index 1b9426c0e5..b39f7d583b 100644
--- a/actionview/lib/action_view/tasks/dependencies.rake
+++ b/actionview/lib/action_view/tasks/dependencies.rake
@@ -2,16 +2,20 @@ namespace :cache_digests do
desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
task :nested_dependencies => :environment do
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- template, format = ENV['TEMPLATE'].split(".")
- format ||= :html
- puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).nested_dependencies
+ puts JSON.pretty_generate ActionView::Digestor.new(name: template_name, finder: finder).nested_dependencies
end
desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
task :dependencies => :environment do
abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- template, format = ENV['TEMPLATE'].split(".")
- format ||= :html
- puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).dependencies
+ puts JSON.pretty_generate ActionView::Digestor.new(name: template_name, finder: finder).dependencies
+ end
+
+ def template_name
+ ENV['TEMPLATE'].split('.', 2).first
+ end
+
+ def finder
+ ApplicationController.new.lookup_context
end
end
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index d9cddc0040..33bfcb458c 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -32,6 +32,15 @@ module ActionView #:nodoc:
@@template_extensions = nil
end
+ # Opposite to register_template_handler.
+ def unregister_template_handler(*extensions)
+ extensions.each do |extension|
+ handler = @@template_handlers.delete extension.to_sym
+ @@default_template_handlers = nil if @@default_template_handlers == handler
+ end
+ @@template_extensions = nil
+ end
+
def template_handler_extensions
@@template_handlers.keys.map {|key| key.to_s }.sort
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/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/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 9eae3a4fbd..7c71fdabd1 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -274,7 +274,6 @@ ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
module ActionController
class Base
- include ActionController::Testing
# This stub emulates the Railtie including the URL helpers from a Rails application
include SharedTestRoutes.url_helpers
include SharedTestRoutes.mounted_helpers
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/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index b44f57a23e..bd345fe873 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -6,9 +6,6 @@ require 'active_support/core_ext/array/extract_options'
# method has access to the view_paths array when looking for a layout to automatically assign.
old_load_paths = ActionController::Base.view_paths
-ActionView::Template::register_template_handler :mab,
- lambda { |template| template.source.inspect }
-
ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../../fixtures/actionpack/layout_tests/' ]
class LayoutTest < ActionController::Base
@@ -17,6 +14,15 @@ class LayoutTest < ActionController::Base
self.view_paths = ActionController::Base.view_paths.dup
end
+module TemplateHandlerHelper
+ def with_template_handler(*extensions, handler)
+ ActionView::Template.register_template_handler(*extensions, handler)
+ yield
+ ensure
+ ActionView::Template.unregister_template_handler(*extensions)
+ end
+end
+
# Restore view_paths to previous value
ActionController::Base.view_paths = old_load_paths
@@ -39,6 +45,8 @@ class MultipleExtensions < LayoutTest
end
class LayoutAutoDiscoveryTest < ActionController::TestCase
+ include TemplateHandlerHelper
+
def setup
super
@request.host = "www.nextangle.com"
@@ -57,10 +65,12 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase
end
def test_third_party_template_library_auto_discovers_layout
- @controller = ThirdPartyTemplateLibraryController.new
- get :hello
- assert_response :success
- assert_equal 'layouts/third_party_template_library.mab', @response.body
+ with_template_handler :mab, lambda { |template| template.source.inspect } do
+ @controller = ThirdPartyTemplateLibraryController.new
+ get :hello
+ assert_response :success
+ assert_equal 'layouts/third_party_template_library.mab', @response.body
+ end
end
def test_namespaced_controllers_auto_detect_layouts1
@@ -135,6 +145,7 @@ end
class LayoutSetInResponseTest < ActionController::TestCase
include ActionView::Template::Handlers
+ include TemplateHandlerHelper
def test_layout_set_when_using_default_layout
@controller = DefaultLayoutController.new
@@ -191,9 +202,11 @@ class LayoutSetInResponseTest < ActionController::TestCase
end
def test_layout_set_when_using_render
- @controller = SetsLayoutInRenderController.new
- get :hello
- assert_template :layout => "layouts/third_party_template_library"
+ with_template_handler :mab, lambda { |template| template.source.inspect } do
+ @controller = SetsLayoutInRenderController.new
+ get :hello
+ assert_template :layout => "layouts/third_party_template_library"
+ end
end
def test_layout_is_not_set_when_none_rendered
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/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/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 938f1c3e54..f213da5934 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -207,29 +207,6 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal "", @av.with_output_buffer {}
end
- def test_flush_output_buffer_concats_output_buffer_to_response
- view = view_with_controller
- assert_equal [], view.response.body_parts
-
- view.output_buffer << 'OMG'
- view.flush_output_buffer
- assert_equal ['OMG'], view.response.body_parts
- assert_equal '', view.output_buffer
-
- view.output_buffer << 'foobar'
- view.flush_output_buffer
- assert_equal ['OMG', 'foobar'], view.response.body_parts
- assert_equal '', view.output_buffer
- end
-
- def test_flush_output_buffer_preserves_the_encoding_of_the_output_buffer
- view = view_with_controller
- alt_encoding = alt_encoding(view.output_buffer)
- view.output_buffer.force_encoding(alt_encoding)
- flush_output_buffer
- assert_equal alt_encoding, view.output_buffer.encoding
- end
-
def alt_encoding(output_buffer)
output_buffer.encoding == Encoding::US_ASCII ? Encoding::UTF_8 : Encoding::US_ASCII
end
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index df3a0602d1..6c780f2297 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -31,6 +31,7 @@ class DependencyTrackerTest < ActionView::TestCase
end
def teardown
+ ActionView::Template.unregister_template_handler :neckbeard
tracker.remove_tracker(:neckbeard)
end
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index 47e1f6a6e5..c2b8439df3 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -233,6 +233,7 @@ class TemplateDigestorTest < ActionView::TestCase
assert_digest_difference("messages/edit", true) do
change_template("comments/_comment")
end
+ ensure
ActionView::Resolver.caching = resolver_before
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 90fe9fdc6a..48073225cb 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -10,15 +10,20 @@ class FormHelperTest < ActionView::TestCase
@output_buffer = super
end
- def setup
- super
+ teardown do
+ I18n.backend.reload!
+ end
+ setup do
# Create "label" locale for testing I18n label helpers
I18n.backend.store_translations 'label', {
activemodel: {
attributes: {
post: {
cost: "Total cost"
+ },
+ :"post/language" => {
+ spanish: "Espanol"
}
}
},
@@ -154,6 +159,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 +276,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>',
diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb
index 4703111741..9ba7f64ad1 100644
--- a/actionview/test/template/javascript_helper_test.rb
+++ b/actionview/test/template/javascript_helper_test.rb
@@ -12,14 +12,14 @@ class JavaScriptHelperTest < ActionView::TestCase
yield if block_given?
end
- def setup
- super
+ setup do
+ @old_escape_html_entities_in_json = ActiveSupport.escape_html_entities_in_json
ActiveSupport.escape_html_entities_in_json = true
@template = self
end
def teardown
- ActiveSupport.escape_html_entities_in_json = false
+ ActiveSupport.escape_html_entities_in_json = @old_escape_html_entities_in_json
end
def test_escape_javascript
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index adb888319d..b59883b760 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -48,6 +48,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "-111.235", number_with_precision(-111.2346)
assert_equal "111.00", number_with_precision(111, precision: 2)
assert_equal "0.00100", number_with_precision(0.001, precision: 5)
+ assert_equal "3.33", number_with_precision(Rational(10, 3), precision: 2)
end
def test_number_to_human_size
@@ -113,6 +114,8 @@ class NumberHelperTest < ActionView::TestCase
I18n.backend.store_translations 'ts',
:custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
assert_equal "1.01 cm", number_to_human(0.0101, :locale => 'ts', :units => :custom_units_for_number_to_human)
+ ensure
+ I18n.reload!
end
def test_number_helpers_outputs_are_html_safe
diff --git a/actionview/test/template/output_buffer_test.rb b/actionview/test/template/output_buffer_test.rb
deleted file mode 100644
index eb0df3d1ab..0000000000
--- a/actionview/test/template/output_buffer_test.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'abstract_unit'
-
-class OutputBufferTest < ActionController::TestCase
- class TestController < ActionController::Base
- def index
- render :text => 'foo'
- end
- end
-
- tests TestController
-
- def setup
- @vc = @controller.view_context
- get :index
- assert_equal ['foo'], body_parts
- end
-
- test 'output buffer is nil after rendering' do
- assert_nil output_buffer
- end
-
- test 'flushing ignores nil output buffer' do
- @controller.view_context.flush_output_buffer
- assert_nil output_buffer
- assert_equal ['foo'], body_parts
- end
-
- test 'flushing ignores empty output buffer' do
- @vc.output_buffer = ''
- @vc.flush_output_buffer
- assert_equal '', output_buffer
- assert_equal ['foo'], body_parts
- end
-
- test 'flushing appends the output buffer to the body parts' do
- @vc.output_buffer = 'bar'
- @vc.flush_output_buffer
- assert_equal '', output_buffer
- assert_equal ['foo', 'bar'], body_parts
- end
-
- test 'flushing preserves output buffer encoding' do
- original_buffer = ' '.force_encoding(Encoding::EUC_JP)
- @vc.output_buffer = original_buffer
- @vc.flush_output_buffer
- assert_equal ['foo', original_buffer], body_parts
- assert_not_equal original_buffer, output_buffer
- assert_equal Encoding::EUC_JP, output_buffer.encoding
- end
-
- protected
- def output_buffer
- @vc.output_buffer
- end
-
- def body_parts
- @controller.response.body_parts
- end
-end
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index 76c71c9e6d..a1bf0e1a5f 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -25,4 +25,11 @@ class OutputSafetyHelperTest < ActionView::TestCase
assert_equal "<p>foo</p><br /><p>bar</p>", joined
end
-end \ No newline at end of file
+ test "safe_join should work recursively similarly to Array.join" do
+ joined = safe_join(['a',['b','c']], ':')
+ assert_equal 'a:b:c', joined
+
+ joined = safe_join(['"a"',['<b>','<c>']], ' <br/> ')
+ assert_equal '&quot;a&quot; &lt;br/&gt; &lt;b&gt; &lt;br/&gt; &lt;c&gt;', joined
+ end
+end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index ca508abfb8..67f1aabbd2 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -12,7 +12,6 @@ module RenderTestCases
@controller_view = TestController.new.view_context
# Reload and register danish language for testing
- I18n.reload!
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
@@ -369,23 +368,40 @@ module RenderTestCases
def test_render_inline_with_render_from_to_proc
ActionView::Template.register_template_handler :ruby_handler, :source.to_proc
- assert_equal '3', @view.render(:inline => "(1 + 2).to_s", :type => :ruby_handler)
+ assert_equal '3', @view.render(inline: "(1 + 2).to_s", type: :ruby_handler)
+ ensure
+ ActionView::Template.unregister_template_handler :ruby_handler
end
def test_render_inline_with_compilable_custom_type
ActionView::Template.register_template_handler :foo, CustomHandler
- assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo)
+ assert_equal 'source: "Hello, World!"', @view.render(inline: "Hello, World!", type: :foo)
+ ensure
+ ActionView::Template.unregister_template_handler :foo
end
def test_render_inline_with_locals_and_compilable_custom_type
ActionView::Template.register_template_handler :foo, CustomHandler
- assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
+ assert_equal 'source: "Hello, <%= name %>!"', @view.render(inline: "Hello, <%= name %>!", locals: { name: "Josh" }, type: :foo)
+ ensure
+ ActionView::Template.unregister_template_handler :foo
end
def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization
ActionView::Template::Handlers.extensions
ActionView::Template.register_template_handler :foo, CustomHandler
assert ActionView::Template::Handlers.extensions.include?(:foo)
+ ensure
+ ActionView::Template.unregister_template_handler :foo
+ end
+
+ def test_render_does_not_use_unregistered_extension_and_template_handler
+ ActionView::Template.register_template_handler :foo, CustomHandler
+ ActionView::Template.unregister_template_handler :foo
+ assert_not ActionView::Template::Handlers.extensions.include?(:foo)
+ assert_equal "Hello, World!", @view.render(inline: "Hello, World!", type: :foo)
+ ensure
+ ActionView::Template::Handlers.class_variable_get(:@@template_handlers).delete(:foo)
end
def test_render_ignores_templates_with_malformed_template_handlers
@@ -474,7 +490,9 @@ module RenderTestCases
def test_render_with_passing_couple_extensions_to_one_register_template_handler_function_call
ActionView::Template.register_template_handler :foo1, :foo2, CustomHandler
- assert_equal @view.render(:inline => "Hello, World!", :type => :foo1), @view.render(:inline => "Hello, World!", :type => :foo2)
+ assert_equal @view.render(inline: "Hello, World!", type: :foo1), @view.render(inline: "Hello, World!", type: :foo2)
+ ensure
+ ActionView::Template.unregister_template_handler :foo1, :foo2
end
def test_render_throws_exception_when_no_extensions_passed_to_register_template_handler_function_call
@@ -494,6 +512,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase
def teardown
GC.start
+ I18n.reload!
end
end
@@ -511,6 +530,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase
def teardown
GC.start
+ I18n.reload!
end
def test_render_utf8_template_with_magic_comment
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/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index fb016a52de..c78b6450f2 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -80,11 +80,27 @@ class TagHelperTest < ActionView::TestCase
str = content_tag('p', "limelight", :class => ["song", "play"])
assert_equal "<p class=\"song play\">limelight</p>", str
+
+ str = content_tag('p', "limelight", :class => ["song", ["play"]])
+ assert_equal "<p class=\"song play\">limelight</p>", str
end
def test_content_tag_with_unescaped_array_class
str = content_tag('p', "limelight", {:class => ["song", "play>"]}, false)
assert_equal "<p class=\"song play>\">limelight</p>", str
+
+ str = content_tag('p', "limelight", {:class => ["song", ["play>"]]}, false)
+ assert_equal "<p class=\"song play>\">limelight</p>", str
+ end
+
+ def test_content_tag_with_empty_array_class
+ str = content_tag('p', 'limelight', {:class => []})
+ assert_equal '<p class="">limelight</p>', str
+ end
+
+ def test_content_tag_with_unescaped_empty_array_class
+ str = content_tag('p', 'limelight', {:class => []}, false)
+ assert_equal '<p class="">limelight</p>', str
end
def test_content_tag_with_data_attributes
@@ -115,6 +131,14 @@ class TagHelperTest < ActionView::TestCase
end
end
+ def test_tag_honors_html_safe_with_escaped_array_class
+ str = tag('p', :class => ['song>', 'play>'.html_safe])
+ assert_equal '<p class="song&gt; play>" />', str
+
+ str = tag('p', :class => ['song>'.html_safe, 'play>'])
+ assert_equal '<p class="song> play&gt;" />', str
+ end
+
def test_skip_invalid_escaped_attributes
['&1;', '&#1dfa3;', '& #123;'].each do |escaped|
assert_equal %(<a href="#{escaped.gsub(/&/, '&amp;')}" />), tag('a', :href => escaped)
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..41f6770f23 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -6,7 +6,7 @@ class TranslationHelperTest < ActiveSupport::TestCase
attr_reader :request, :view
- def setup
+ setup do
I18n.backend.store_translations(:en,
:translations => {
:templates => {
@@ -30,6 +30,10 @@ class TranslationHelperTest < ActiveSupport::TestCase
@view = ::ActionView::Base.new(ActionController::Base.view_paths, {})
end
+ teardown do
+ I18n.backend.reload!
+ end
+
def test_delegates_to_i18n_setting_the_rescue_format_option_to_html
I18n.expects(:translate).with(:foo, :locale => 'en', :raise=>true).returns("")
translate :foo, :locale => 'en'
@@ -151,4 +155,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/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 0db220ab8f..4d9186017f 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,6 +1,24 @@
+* `has_secure_password` now verifies that the given password is less than 72
+ characters if validations are enabled.
+
+ Fixes #14591.
+
+ *Akshay Vishnoi*
+
+* Remove deprecated `Validator#setup` without replacement.
+
+ See #10716.
+
+ *Kuldeep Aggarwal*
+
+* 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/README.rdoc b/activemodel/README.rdoc
index 500be2a04a..f6beff14e1 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -147,7 +147,7 @@ behavior out of the box:
extend ActiveModel::Naming
end
- NamedPerson.model_name # => "NamedPerson"
+ NamedPerson.model_name.name # => "NamedPerson"
NamedPerson.model_name.human # => "Named person"
{Learn more}[link:classes/ActiveModel/Naming.html]
@@ -262,6 +262,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 374265f0d8..9c9b6f4a77 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -40,13 +40,15 @@ module ActiveModel
self
end
- # Returns an Enumerable of all key attributes if any is set, regardless if
+ # Returns an Array of all key attributes if any is set, regardless if
# the object is persisted or not. Returns +nil+ if there are no key attributes.
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Conversion
+ # attr_accessor :id
# end
#
- # person = Person.create
+ # person = Person.create(id: 1)
# person.to_key # => [1]
def to_key
key = respond_to?(:id) && id
@@ -56,10 +58,15 @@ module ActiveModel
# Returns a +string+ representing the object's key suitable for use in URLs,
# or +nil+ if <tt>persisted?</tt> is +false+.
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Conversion
+ # attr_accessor :id
+ # def persisted?
+ # true
+ # end
# end
#
- # person = Person.create
+ # person = Person.create(id: 1)
# person.to_param # => "1"
def to_param
(persisted? && key = to_key) ? key.join('-') : nil
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/model.rb b/activemodel/lib/active_model/model.rb
index 63716eebb1..640024eaa1 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -16,8 +16,8 @@ module ActiveModel
# end
#
# person = Person.new(name: 'bob', age: '18')
- # person.name # => 'bob'
- # person.age # => 18
+ # person.name # => "bob"
+ # person.age # => "18"
#
# Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
# to return +false+, which is the most common case. You may want to override
@@ -74,7 +74,7 @@ module ActiveModel
#
# person = Person.new(name: 'bob', age: '18')
# person.name # => "bob"
- # person.age # => 18
+ # person.age # => "18"
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 11ebfe6cc0..5219de2606 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -204,7 +204,7 @@ module ActiveModel
# extend ActiveModel::Naming
# end
#
- # BookCover.model_name # => "BookCover"
+ # BookCover.model_name.name # => "BookCover"
# BookCover.model_name.human # => "Book cover"
#
# BookCover.model_name.i18n_key # => :book_cover
@@ -218,10 +218,11 @@ module ActiveModel
# used to retrieve all kinds of naming-related information
# (See ActiveModel::Name for more information).
#
- # class Person < ActiveModel::Model
+ # class Person
+ # include ActiveModel::Model
# end
#
- # Person.model_name # => Person
+ # Person.model_name.name # => "Person"
# Person.model_name.class # => ActiveModel::Name
# Person.model_name.singular # => "person"
# Person.model_name.plural # => "people"
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 826e89bf9d..fdfd8cb147 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -2,6 +2,11 @@ module ActiveModel
module SecurePassword
extend ActiveSupport::Concern
+ # BCrypt hash function can handle maximum 72 characters, and if we pass
+ # password of length more than 72 characters it ignores extra characters.
+ # Hence need to put a restriction on password length.
+ MAX_PASSWORD_LENGTH_ALLOWED = 72
+
class << self
attr_accessor :min_cost # :nodoc:
end
@@ -11,16 +16,20 @@ module ActiveModel
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a +password_digest+ attribute.
#
- # Validations for presence of password on create, confirmation of password
- # (using a +password_confirmation+ attribute) are automatically added. If
- # you wish to turn off validations, pass <tt>validations: false</tt> as an
- # argument. You can add more validations by hand if need be.
+ # The following validations are added automatically:
+ # * Password must be present on creation
+ # * Password length should be less than or equal to 72 characters
+ # * Confirmation of password (using a +password_confirmation+ attribute)
+ #
+ # If password confirmation validation is not needed, simply leave out the
+ # value for +password_confirmation+ (i.e. don't provide a form field for
+ # it). When this attribute has a +nil+ value, the validation will not be
+ # triggered.
#
- # If you don't need the confirmation validation, just don't set any
- # value to the password_confirmation attribute and the validation
- # will not be triggered.
+ # For further customizability, it is possible to supress the default
+ # validations by passing <tt>validations: false</tt> as an argument.
#
- # You need to add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
+ # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
#
# gem 'bcrypt', '~> 3.1.7'
#
@@ -52,8 +61,6 @@ module ActiveModel
raise
end
- attr_reader :password
-
include InstanceMethodsOnActivation
if options.fetch(:validations, true)
@@ -65,9 +72,11 @@ module ActiveModel
record.errors.add(:password, :blank) unless record.password_digest.present?
end
+ validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
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 +100,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/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index bddacc8c45..65cb1e5a88 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -106,7 +106,6 @@ module ActiveModel
# Accepts options that will be made available through the +options+ reader.
def initialize(options = {})
@options = options.except(:class).freeze
- deprecated_setup(options)
end
# Returns the kind for this validator.
@@ -122,21 +121,6 @@ module ActiveModel
def validate(record)
raise NotImplementedError, "Subclasses must implement a validate(record) method."
end
-
- private
- def deprecated_setup(options) # TODO: remove me in 4.2.
- return unless respond_to?(:setup)
- ActiveSupport::Deprecation.warn "The `Validator#setup` instance method is deprecated and will be removed on Rails 4.2. Do your setup in the constructor instead:
-
-class MyValidator < ActiveModel::Validator
- def initialize(options={})
- super
- options[:class].send :attr_accessor, :custom_attribute
- end
-end
-"
- setup(options[:class])
- end
end
# +EachValidator+ is a validator which iterates through the attributes given
diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb
index c5cfbf909d..800cad6d9a 100644
--- a/activemodel/test/cases/conversion_test.rb
+++ b/activemodel/test/cases/conversion_test.rb
@@ -24,6 +24,10 @@ class ConversionTest < ActiveModel::TestCase
assert_equal "1", Contact.new(id: 1).to_param
end
+ test "to_param returns the string joined by '-'" do
+ assert_equal "abc-xyz", Contact.new(id: ["abc", "xyz"]).to_param
+ end
+
test "to_param returns nil if to_key is nil" do
klass = Class.new(Contact) do
def persisted?
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index bcd1e04a0f..e59f00c8c5 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -20,7 +20,7 @@ class SecurePasswordTest < ActiveModel::TestCase
ActiveModel::SecurePassword.min_cost = @original_min_cost
end
- test "create and updating without validations" do
+ test "create/update without validations" do
assert @visitor.valid?(:create), 'visitor should be valid'
assert @visitor.valid?(:update), 'visitor should be valid'
@@ -31,6 +31,18 @@ class SecurePasswordTest < ActiveModel::TestCase
assert @visitor.valid?(:update), 'visitor should be valid'
end
+ test "create a new user with validations and valid password/confirmation" do
+ @user.password = 'password'
+ @user.password_confirmation = 'password'
+
+ assert @user.valid?(:create), 'user should be valid'
+
+ @user.password = 'a' * 72
+ @user.password_confirmation = 'a' * 72
+
+ assert @user.valid?(:create), 'user should be valid'
+ end
+
test "create a new user with validation and a blank password" do
@user.password = ''
assert !@user.valid?(:create), 'user should be invalid'
@@ -45,6 +57,14 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_equal ["can't be blank"], @user.errors[:password]
end
+ test 'create a new user with validation and password length greater than 72' do
+ @user.password = 'a' * 73
+ @user.password_confirmation = 'a' * 73
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert_equal 1, @user.errors.count
+ assert_equal ["is too long (maximum is 72 characters)"], @user.errors[:password]
+ end
+
test "create a new user with validation and a blank password confirmation" do
@user.password = 'password'
@user.password_confirmation = ''
@@ -67,15 +87,19 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
end
- test "create a new user with validation and a correct password confirmation" do
- @user.password = 'password'
- @user.password_confirmation = 'something else'
- assert !@user.valid?(:create), 'user should be invalid'
- assert_equal 1, @user.errors.count
- assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
+ test "update an existing user with validation and no change in password" do
+ assert @existing_user.valid?(:update), 'user should be valid'
end
- test "update an existing user with validation and no change in password" do
+ test "update an existing user with validations and valid password/confirmation" do
+ @existing_user.password = 'password'
+ @existing_user.password_confirmation = 'password'
+
+ assert @existing_user.valid?(:update), 'user should be valid'
+
+ @existing_user.password = 'a' * 72
+ @existing_user.password_confirmation = 'a' * 72
+
assert @existing_user.valid?(:update), 'user should be valid'
end
@@ -97,6 +121,14 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_equal ["can't be blank"], @existing_user.errors[:password]
end
+ test 'updating an existing user with validation and password length greater than 72' do
+ @existing_user.password = 'a' * 73
+ @existing_user.password_confirmation = 'a' * 73
+ assert !@existing_user.valid?(:update), 'user should be invalid'
+ assert_equal 1, @existing_user.errors.count
+ assert_equal ["is too long (maximum is 72 characters)"], @existing_user.errors[:password]
+ end
+
test "updating an existing user with validation and a blank password confirmation" do
@existing_user.password = 'password'
@existing_user.password_confirmation = ''
@@ -119,14 +151,6 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
end
- test "updating an existing user with validation and a correct password confirmation" do
- @existing_user.password = 'password'
- @existing_user.password_confirmation = 'something else'
- assert !@existing_user.valid?(:update), 'user should be invalid'
- assert_equal 1, @existing_user.errors.count
- assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
- end
-
test "updating an existing user with validation and a blank password digest" do
@existing_user.password_digest = ''
assert !@existing_user.valid?(:update), 'user should be invalid'
diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb
index 795ce16d28..ebfe1cf4e4 100644
--- a/activemodel/test/cases/validations/absence_validation_test.rb
+++ b/activemodel/test/cases/validations/absence_validation_test.rb
@@ -11,7 +11,7 @@ class AbsenceValidationTest < ActiveModel::TestCase
CustomReader.clear_validators!
end
- def test_validate_absences
+ def test_validates_absence_of
Topic.validates_absence_of(:title, :content)
t = Topic.new
t.title = "foo"
@@ -23,11 +23,12 @@ class AbsenceValidationTest < ActiveModel::TestCase
t.content = "something"
assert t.invalid?
assert_equal ["must be blank"], t.errors[:content]
+ assert_equal [], t.errors[:title]
t.content = ""
assert t.valid?
end
- def test_accepts_array_arguments
+ def test_validates_absence_of_with_array_arguments
Topic.validates_absence_of %w(title content)
t = Topic.new
t.title = "foo"
@@ -37,7 +38,7 @@ class AbsenceValidationTest < ActiveModel::TestCase
assert_equal ["must be blank"], t.errors[:content]
end
- def test_validates_acceptance_of_with_custom_error_using_quotes
+ def test_validates_absence_of_with_custom_error_using_quotes
Person.validates_absence_of :karma, message: "This string contains 'single' and \"double\" quotes"
p = Person.new
p.karma = "good"
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/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 6a74ee353d..4fee704ef5 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -378,25 +378,4 @@ class ValidationsTest < ActiveModel::TestCase
assert topic.invalid?
assert duped.valid?
end
-
- # validator test:
- def test_setup_is_deprecated_but_still_receives_klass # TODO: remove me in 4.2.
- validator_class = Class.new(ActiveModel::Validator) do
- def setup(klass)
- @old_klass = klass
- end
-
- def validate(*)
- @old_klass == Topic or raise "#setup didn't work"
- end
- end
-
- assert_deprecated do
- Topic.validates_with validator_class
- end
-
- t = Topic.new
- t.valid?
- end
-
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 74f5666de0..39c8769814 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,412 @@
+* Deprecate `serialized_attributes` without replacement. You can access its
+ behavior by going through the column's type object.
+
+ *Sean Griffin*
+
+* Correctly extract IPv6 addresses from `DATABASE_URI`: the square brackets
+ are part of the URI structure, not the actual host.
+
+ Fixes #15705.
+
+ *Andy Bakun*, *Aaron Stone*
+
+* Ensure both parent IDs are set on join records when both sides of a
+ through association are new.
+
+ *Sean Griffin*
+
+* `ActiveRecord::Dirty` now detects in-place changes to mutable values.
+ Serialized attributes on ActiveRecord models will no longer save when
+ unchanged. Fixes #8328.
+
+ Sean Griffin
+
+* Fixed automatic maintaining test schema to properly handle sql structure
+ schema format.
+
+ Fixes #15394.
+
+ *Wojciech Wnętrzak*
+
+* Pluck now works when selecting columns from different tables with the same
+ name.
+
+ Fixes #15649
+
+ *Sean Griffin*
+
+* Remove `cache_attributes` and friends. All attributes are cached.
+
+ *Sean Griffin*
+
+* Remove deprecated method `ActiveRecord::Base.quoted_locking_column`.
+
+ *Akshay Vishnoi*
+
+* `ActiveRecord::FinderMethods.find` with block can handle proc parameter as
+ `Enumerable#find` does.
+
+ Fixes #15382.
+
+ *James Yang*
+
+* Make timezone aware attributes work with PostgreSQL array columns.
+
+ Fixes #13402.
+
+ *Kuldeep Aggarwal*, *Sean Griffin*
+
+* `ActiveRecord::SchemaMigration` has no primary key regardless of the
+ `primary_key_prefix_type` configuration.
+
+ Fixes #15051.
+
+ *JoseLuis Torres*, *Yves Senn*
+
+* `rake db:migrate:status` works with legacy migration numbers like `00018_xyz.rb`.
+
+ Fixes #15538.
+
+ *Yves Senn*
+
+* Baseclass becomes! subclass.
+
+ Before this change, a record which changed its STI type, could not be
+ updated.
+
+ Fixes #14785.
+
+ *Matthew Draper*, *Earl St Sauver*, *Edo Balvers*
+
+* Remove deprecated `ActiveRecord::Migrator.proper_table_name`. Use the
+ `proper_table_name` instance method on `ActiveRecord::Migration` instead.
+
+ *Akshay Vishnoi*
+
+* Fix regression on eager loading association based on SQL query rather than
+ existing column.
+
+ Fixes #15480.
+
+ *Lauro Caetano*, *Carlos Antonio da Silva*
+
+* Return a null column from `column_for_attribute` when no column exists.
+
+ *Sean Griffin*
+
+* Implemented ActiveRecord::Base#pretty_print to work with PP.
+
+ *Ethan*
+
+* Preserve type when dumping PostgreSQL point, bit, bit varying and money
+ columns.
+
+ *Yves Senn*
+
+* New records remain new after YAML serialization.
+
+ *Sean Griffin*
+
+* PostgreSQL support default values for enum types. Fixes #7814.
+
+ *Yves Senn*
+
+* PostgreSQL `default_sequence_name` respects schema. Fixes #7516.
+
+ *Yves Senn*
+
+* Fixed `columns_for_distinct` of postgresql adapter to work correctly
+ with orders without sort direction modifiers.
+
+ *Nikolay Kondratyev*
+
+* PostgreSQL `reset_pk_sequence!` respects schemas. Fixes #14719.
+
+ *Yves Senn*
+
+* Keep PostgreSQL `hstore` and `json` attributes as `Hash` in `@attributes`.
+ Fixes duplication in combination with `store_accessor`.
+
+ Fixes #15369.
+
+ *Yves Senn*
+
+* `rake railties:install:migrations` respects the order of railties.
+
+ *Arun Agrawal*
+
+* Fix redefine a has_and_belongs_to_many inside inherited class
+ Fixing regression case, where redefining the same has_an_belongs_to_many
+ definition into a subclass would raise.
+
+ Fixes #14983.
+
+ *arthurnn*
+
+* Fix has_and_belongs_to_many public reflection.
+ When defining a has_and_belongs_to_many, internally we convert that to two has_many.
+ But as `reflections` is a public API, people expect to see the right macro.
+
+ Fixes #14682.
+
+ *arthurnn*
+
+* Fixed serialization for records with an attribute named `format`.
+
+ Fixes #15188.
+
+ *Godfrey Chan*
+
+* When a `group` is set, `sum`, `size`, `average`, `minimum` and `maximum`
+ on a NullRelation should return a Hash.
+
+ *Kuldeep Aggarwal*
+
+* 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 relationship 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 has_and_belongs_to_many 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 primary key 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 has_and_belongs_to_many's CollectionAssociation size calculation.
+
+ has_and_belongs_to_many 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 variables keys of MySQL connection configuration.
+
+ When `sql_mode` variable for MySQL adapters set in configuration as `String`
+ was ignored and overwritten by 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*
@@ -14,7 +423,7 @@
*Eric Chahin*
-* `sanitize_sql_like` helper method to escape a string for safe use in a SQL
+* `sanitize_sql_like` helper method to escape a string for safe use in an SQL
LIKE statement.
Example:
@@ -50,7 +459,7 @@
*Lauro Caetano*
* Calling `delete_all` on an unloaded `CollectionProxy` no longer
- generates a SQL statement containing each id of the collection:
+ generates an SQL statement containing each id of the collection:
Before:
@@ -91,7 +500,7 @@
* Auto-generate stable fixture UUIDs on PostgreSQL.
- Fixes: #11524
+ Fixes #11524.
*Roderick van Domburg*
@@ -175,10 +584,6 @@
*Luke Steensen*
-* Make possible to change `record_timestamps` inside Callbacks.
-
- *Tieg Zaharia*
-
* Fixed error where .persisted? throws SystemStackError for an unsaved model with a
custom primary key that didn't save due to validation error.
@@ -209,12 +614,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`.
@@ -274,12 +673,11 @@
*arthurnn*
-* Passing an Active Record object to `find` is now deprecated. Call `.id`
- on the object first.
-
* Passing an Active Record object to `find` or `exists?` is now deprecated.
Call `.id` on the object first.
+ *Aaron Patterson*
+
* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation.
*Ryuta Kamizono*
@@ -293,6 +691,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..f4777919d3 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
@@ -20,8 +20,10 @@ 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,10 +31,8 @@ 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.
@@ -130,7 +130,7 @@ A short rundown of some of the major features:
SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
-* Logging support for Log4r[http://log4r.rubyforge.org] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
+* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
@@ -208,6 +208,11 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
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.rb b/activerecord/lib/active_record.rb
index f856c482d6..53fa132219 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -31,6 +31,7 @@ require 'active_record/version'
module ActiveRecord
extend ActiveSupport::Autoload
+ autoload :Attribute
autoload :Base
autoload :Callbacks
autoload :Core
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 0d5313956b..e576ec4d40 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -129,10 +129,10 @@ module ActiveRecord
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
# converted to an instance of value class if necessary.
#
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
- # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
- # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
- # values can be assigned to the value object using either another NetAddr::CIDR object, a string
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be
+ # aggregated using the NetAddr::CIDR value class (http://www.ruby-doc.org/gems/docs/n/netaddr-1.5.0/NetAddr/CIDR.html).
+ # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter.
+ # New values can be assigned to the value object using either another NetAddr::CIDR object, a string
# or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
# these requirements:
#
@@ -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)
@@ -244,15 +244,19 @@ module ActiveRecord
def writer_method(name, class_name, mapping, allow_nil, converter)
define_method("#{name}=") do |part|
klass = class_name.constantize
+ if part.is_a?(Hash)
+ part = klass.new(*part.values)
+ end
+
unless part.is_a?(klass) || converter.nil? || part.nil?
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
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/associations.rb b/activerecord/lib/active_record/associations.rb
index 4abe2ad0a0..6222bfe903 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -50,7 +50,7 @@ module ActiveRecord
def initialize(reflection)
through_reflection = reflection.through_reflection
source_reflection_names = reflection.source_reflection_names
- source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
+ source_associations = reflection.through_reflection.klass._reflections.keys
super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
end
end
@@ -151,7 +151,7 @@ module ActiveRecord
association = association_instance_get(name)
if association.nil?
- raise AssociationNotFoundError.new(self, name) unless reflection = self.class.reflect_on_association(name)
+ raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
@@ -202,12 +202,13 @@ module ActiveRecord
# For instance, +attributes+ and +connection+ would be bad choices for association names.
#
# == Auto-generated methods
+ # See also Instance Public methods below for more details.
#
# === Singular associations (one-to-one)
# | | belongs_to |
# generated methods | belongs_to | :polymorphic | has_one
# ----------------------------------+------------+--------------+---------
- # other | X | X | X
+ # other(force_reload=false) | X | X | X
# other=(other) | X | X | X
# build_other(attributes={}) | X | | X
# create_other(attributes={}) | X | | X
@@ -217,7 +218,7 @@ module ActiveRecord
# | | | has_many
# generated methods | habtm | has_many | :through
# ----------------------------------+-------+----------+----------
- # others | X | X | X
+ # others(force_reload=false) | X | X | X
# others=(other,other,...) | X | X | X
# other_ids | X | X | X
# other_ids=(id,id,...) | X | X | X
@@ -419,6 +420,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 +778,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'
@@ -1567,14 +1577,22 @@ module ActiveRecord
scope = nil
end
+ habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(:has_and_belongs_to_many, name, scope, options, self)
+
builder = Builder::HasAndBelongsToMany.new name, self, options
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
Reflection.add_reflection self, middle_reflection.name, middle_reflection
+ middle_reflection.parent_reflection = [name.to_s, habtm_reflection]
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -1590,11 +1608,12 @@ 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
has_many name, scope, hm_options, &extension
+ self._reflections[name.to_s].parent_reflection = [name.to_s, habtm_reflection]
end
end
end
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 85109aee6c..a6a1947148 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -32,8 +32,18 @@ module ActiveRecord
join.left.downcase.scan(
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
).size
- else
+ elsif join.respond_to? :left
join.left.table_name == name ? 1 : 0
+ else
+ # this branch is reached by two tests:
+ #
+ # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
+ # with :posts
+ #
+ # activerecord/test/cases/associations/eager_test.rb:1133
+ # with :comments
+ #
+ 0
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 9ad2d2fb12..f1c36cd047 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -160,7 +160,7 @@ module ActiveRecord
def marshal_load(data)
reflection_name, ivars = data
ivars.each { |name, val| instance_variable_set(name, val) }
- @reflection = @owner.class.reflect_on_association(reflection_name)
+ @reflection = @owner.class._reflect_on_association(reflection_name)
end
def initialize_attributes(record) #:nodoc:
@@ -179,7 +179,7 @@ module ActiveRecord
def creation_attributes
attributes = {}
- if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through]
+ if (reflection.has_one? || reflection.collection?) && !options[:through]
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
if reflection.options[:as]
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 27fd9e35db..519d4d8651 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)
@@ -69,18 +105,9 @@ module ActiveRecord
chain.each_with_index do |reflection, i|
table, foreign_table = tables.shift, tables.first
- if reflection.source_macro == :belongs_to
- if reflection.options[:polymorphic]
- key = reflection.association_primary_key(assoc_klass)
- else
- key = reflection.association_primary_key
- end
-
- foreign_key = reflection.foreign_key
- else
- key = reflection.foreign_key
- foreign_key = reflection.active_record_primary_key
- end
+ join_keys = reflection.join_keys(assoc_klass)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
if reflection == chain.last
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
@@ -120,6 +147,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 1edd4fa3aa..81fdd681de 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -92,7 +92,7 @@ module ActiveRecord
# has_one associations.
def invertible_for?(record)
inverse = inverse_reflection_for(record)
- inverse && inverse.macro == :has_one
+ inverse && inverse.has_one?
end
def target_id
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 47cc1f4b34..3998aca23e 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -103,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..0ad5206980 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
@@ -60,13 +66,13 @@ module ActiveRecord::Associations::Builder
def self.add_left_association(name, options)
belongs_to name, options
- self.left_reflection = reflect_on_association(name)
+ self.left_reflection = _reflect_on_association(name)
end
def self.add_right_association(name, options)
rhs_name = name.to_s.singularize.to_sym
belongs_to rhs_name, options
- self.right_reflection = reflect_on_association(rhs_name)
+ self.right_reflection = _reflect_on_association(rhs_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/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index f359efd496..a1f4f51664 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def valid_options
- valid = super + [:order, :as]
+ valid = super + [:as]
valid += [:through, :source, :source_type] if options[:through]
valid
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 803e3ab9ab..306588ac66 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -57,7 +57,7 @@ module ActiveRecord
def ids_writer(ids)
pk_column = reflection.primary_key_column
ids = Array(ids).reject { |id| id.blank? }
- ids.map! { |i| pk_column.type_cast(i) }
+ ids.map! { |i| pk_column.type_cast_from_user(i) }
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
end
@@ -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/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index aac85a36c8..2727e23870 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -100,28 +100,33 @@ module ActiveRecord
# Hence this method.
def inverse_updates_counter_cache?(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
+ reflection.klass._reflections.values.any? { |inverse_reflection|
+ :belongs_to == inverse_reflection.macro &&
inverse_reflection.counter_cache_column == counter_name
}
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 73baefb8e1..175019a72b 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.read_attribute cached_counter_attribute_name(reflection)
elsif loaded?
target.size
else
- count
+ super
end
end
@@ -72,25 +73,27 @@ 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_scope_attributes
+ through_record = through_association.build(*options_for_through_record)
through_record.send("#{source_reflection.name}=", record)
through_record
end
end
+ def options_for_through_record
+ [through_scope_attributes]
+ end
+
def through_scope_attributes
- scope.where_values_hash(through_association.reflection.name.to_s)
+ scope.where_values_hash(through_association.reflection.name.to_s).except!(through_association.reflection.foreign_key)
end
def save_through_record(record)
@@ -106,9 +109,9 @@ module ActiveRecord
inverse = source_reflection.inverse_of
if inverse
- if inverse.macro == :has_many
+ if inverse.collection?
record.send(inverse.name) << build_through_record(record)
- elsif inverse.macro == :has_one
+ elsif inverse.has_one?
record.send("#{inverse.name}=", build_through_record(record))
end
end
@@ -117,7 +120,7 @@ module ActiveRecord
end
def target_reflection_has_associated_record?
- !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
end
def update_through_counter?(method)
@@ -131,13 +134,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)
@@ -171,7 +174,7 @@ module ActiveRecord
klass.decrement_counter counter, records.map(&:id)
end
- if through_reflection.macro == :has_many && update_through_counter?(method)
+ if through_reflection.collection? && update_through_counter?(method)
update_counter(-count, through_reflection)
end
@@ -181,14 +184,18 @@ module ActiveRecord
def through_records_for(record)
attributes = construct_join_attributes(record)
candidates = Array.wrap(through_association.target)
- candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
+ candidates.find_all do |c|
+ attributes.all? do |key, value|
+ c.public_send(key) == value
+ end
+ end
end
def delete_through_records(records)
records.each do |record|
through_records = through_records_for(record)
- if through_reflection.macro == :has_many
+ if through_reflection.collection?
through_records.each { |r| through_association.target.delete(r) }
else
if through_records.include?(through_association.target)
@@ -202,7 +209,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..fbb4551b22 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -144,7 +144,7 @@ module ActiveRecord
column_aliases = aliases.column_aliases join_root
result_set.each { |row_hash|
- primary_id = type_caster.type_cast row_hash[primary_key]
+ primary_id = type_caster.type_cast_from_database row_hash[primary_key]
parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
}
@@ -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)
@@ -207,7 +207,7 @@ module ActiveRecord
end
def find_reflection(klass, name)
- klass.reflect_on_association(name) or
+ klass._reflect_on_association(name) or
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
end
@@ -215,8 +215,9 @@ module ActiveRecord
associations.map do |name, right|
reflection = find_reflection base_klass, name
reflection.check_validity!
+ reflection.check_eager_loadable!
- if reflection.options[:polymorphic]
+ if reflection.polymorphic?
raise EagerLoadPolymorphicError.new(reflection)
end
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..7519fec10a 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
@@ -142,6 +143,7 @@ module ActiveRecord
def grouped_records(association, records)
h = {}
records.each do |record|
+ next unless record
assoc = record.association(association)
klasses = h[assoc.reflection] ||= {}
(klasses[assoc.klass] ||= []) << record
@@ -175,6 +177,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..33c8619359 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_for_attribute(association_key_name).type
+ end
+
+ def owner_key_type
+ @model.column_for_attribute(owner_key_name).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..f00fef8b9e 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
@@ -39,12 +41,16 @@ module ActiveRecord
def construct_join_attributes(*records)
ensure_mutable
- join_attributes = {
- source_reflection.foreign_key =>
- records.map { |record|
- record.send(source_reflection.association_primary_key(reflection.klass))
- }
- }
+ if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
+ join_attributes = { source_reflection.name => records }
+ else
+ join_attributes = {
+ source_reflection.foreign_key =>
+ records.map { |record|
+ record.send(source_reflection.association_primary_key(reflection.klass))
+ }
+ }
+ end
if options[:source_type]
join_attributes[source_reflection.foreign_type] =
@@ -61,14 +67,13 @@ module ActiveRecord
# Note: this does not capture all cases, for example it would be crazy to try to
# properly support stale-checking for nested associations.
def stale_state
- if through_reflection.macro == :belongs_to
+ if through_reflection.belongs_to?
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
end
end
def foreign_key_present?
- through_reflection.macro == :belongs_to &&
- !owner[through_reflection.foreign_key].nil?
+ through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
end
def ensure_mutable
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
new file mode 100644
index 0000000000..f78cde23c5
--- /dev/null
+++ b/activerecord/lib/active_record/attribute.rb
@@ -0,0 +1,56 @@
+module ActiveRecord
+ class Attribute # :nodoc:
+ class << self
+ def from_database(value, type)
+ FromDatabase.new(value, type)
+ end
+
+ def from_user(value, type)
+ FromUser.new(value, type)
+ end
+ end
+
+ attr_reader :value_before_type_cast, :type
+
+ # This method should not be called directly.
+ # Use #from_database or #from_user
+ def initialize(value_before_type_cast, type)
+ @value_before_type_cast = value_before_type_cast
+ @type = type
+ end
+
+ def value
+ # `defined?` is cheaper than `||=` when we get back falsy values
+ @value = type_cast(value_before_type_cast) unless defined?(@value)
+ @value
+ end
+
+ def value_for_database
+ type.type_cast_for_database(value)
+ end
+
+ def type_cast
+ raise NotImplementedError
+ end
+
+ protected
+
+ def initialize_dup(other)
+ if defined?(@value) && @value.duplicable?
+ @value = @value.dup
+ end
+ end
+
+ class FromDatabase < Attribute
+ def type_cast(value)
+ type.type_cast_from_database(value)
+ end
+ end
+
+ class FromUser < Attribute
+ def type_cast(value)
+ type.type_cast_from_user(value)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 30fa2c8ba5..40e2918777 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", "created_at" => nil, "updated_at" => nil}
+ # cat.assign_attributes(status: "sleeping")
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
+ #
+ # 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."
@@ -117,8 +126,8 @@ module ActiveRecord
def read_value
return if values.values.compact.empty?
- @column = object.class.reflect_on_aggregation(name.to_sym) || object.column_for_attribute(name)
- klass = column.klass
+ @column = object.column_for_attribute(name)
+ klass = column ? column.klass : nil
if klass == Time
read_time
@@ -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
@@ -177,8 +186,7 @@ module ActiveRecord
positions = (1..max_position)
validate_required_parameters!(positions)
- set_values = values.values_at(*positions)
- klass.new(*set_values)
+ values.slice(*positions)
end
# Checks whether some blank date parameter exists. Note that this is different
diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb
new file mode 100644
index 0000000000..596161f81d
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_decorators.rb
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module AttributeDecorators # :nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :attribute_type_decorations, instance_accessor: false # :internal:
+ self.attribute_type_decorations = Hash.new({})
+ end
+
+ module ClassMethods
+ def decorate_attribute_type(column_name, decorator_name, &block)
+ clear_caches_calculated_from_columns
+ column_name = column_name.to_s
+
+ # Create new hashes so we don't modify parent classes
+ decorations_for_column = attribute_type_decorations[column_name]
+ new_decorations = decorations_for_column.merge(decorator_name.to_s => block)
+ self.attribute_type_decorations = attribute_type_decorations.merge(column_name => new_decorations)
+ end
+
+ private
+
+ def add_user_provided_columns(*)
+ super.map do |column|
+ decorations = attribute_type_decorations[column.name].values
+ decorated_type = decorations.inject(column.cast_type) do |type, block|
+ block.call(type)
+ end
+ column.with_type(decorated_type)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 4b1733619a..b4d75d6556 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -18,6 +18,8 @@ module ActiveRecord
include TimeZoneConversion
include Dirty
include Serialization
+
+ delegate :column_for_attribute, to: :class
end
AttrNames = Module.new {
@@ -48,7 +50,11 @@ module ActiveRecord
end
private
- def method_body; raise NotImplementedError; end
+
+ # Override this method in the subclasses for method body.
+ def method_body(method_name, const_name)
+ raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
+ end
end
module ClassMethods
@@ -66,6 +72,7 @@ module ActiveRecord
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods # :nodoc:
+ return false if @attribute_methods_generated
# Use a mutex; we don't want two thread simultaneously trying to define
# attribute methods.
generated_attribute_methods.synchronize do
@@ -149,16 +156,6 @@ module ActiveRecord
end
end
- def find_generated_attribute_method(method_name) # :nodoc:
- klass = self
- until klass == Base
- gen_methods = klass.generated_attribute_methods
- return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
- klass = klass.superclass
- end
- nil
- end
-
# Returns +true+ if +attribute+ is an attribute method and table exists,
# +false+ otherwise.
#
@@ -187,23 +184,25 @@ module ActiveRecord
[]
end
end
- end
- # If we haven't generated any methods yet, generate them, then
- # see if we've created the method we're looking for.
- def method_missing(method, *args, &block) # :nodoc:
- self.class.define_attribute_methods
- if respond_to_without_attributes?(method)
- # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
- # call in a overwritten attribute method
- if attribute_method = self.class.find_generated_attribute_method(method)
- # this is probably horribly slow, but should only happen at most once for a given AR class
- attribute_method.bind(self).call(*args, &block)
- else
- send(method, *args, &block)
+ # Returns the column object for the named attribute.
+ # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
+ # named attribute does not exist.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.new
+ # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
+ # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
+ #
+ # person.column_for_attribute(:nothing)
+ # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
+ def column_for_attribute(name)
+ name = name.to_s
+ columns_hash.fetch(name) do
+ ConnectionAdapters::NullColumn.new(name)
end
- else
- super
end
end
@@ -224,12 +223,8 @@ module ActiveRecord
# person.respond_to('age?') # => true
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
+ return false unless super
name = name.to_s
- self.class.define_attribute_methods
- result = super
-
- # If the result is false the answer is false.
- return false unless result
# If the result is true then check for the select case.
# For queries selecting a subset of columns, return false for unselected columns.
@@ -281,11 +276,6 @@ module ActiveRecord
}
end
- # Placeholder so it can be overriden when needed by serialization
- def attributes_for_coder # :nodoc:
- attributes
- end
-
# Returns an <tt>#inspect</tt>-like string for the value of the
# attribute +attr_name+. String attributes are truncated upto 50
# characters, Date and Time attributes are returned in the
@@ -326,39 +316,24 @@ 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?)
end
- # Returns the column object for the named attribute. Returns +nil+ if the
- # named attribute not exists.
- #
- # class Person < ActiveRecord::Base
- # end
- #
- # person = Person.new
- # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
- # # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
- #
- # person.column_for_attribute(:nothing)
- # # => nil
- def column_for_attribute(name)
- # FIXME: should this return a null object for columns that don't exist?
- self.class.columns_hash[name.to_s]
- end
-
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "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
@@ -392,11 +367,10 @@ module ActiveRecord
protected
- def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
- attribute_names.each do |name|
- attributes[name] = clone_attribute_value(reader_method, name)
+ def clone_attributes # :nodoc:
+ @attributes.each_with_object({}) do |(name, attr), h|
+ h[name] = attr.dup
end
- attributes
end
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
@@ -435,16 +409,16 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
- attribute_names.select do |name|
- column_for_attribute(name) && !readonly_attribute?(name)
+ attribute_names.reject do |name|
+ readonly_attribute?(name)
end
end
# Filters out the primary keys, from the attribute names, when the primary
# key is to be generated (e.g. the id attribute has no value).
def attributes_for_create(attribute_names)
- attribute_names.select do |name|
- column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
+ attribute_names.reject do |name|
+ pk_attribute?(name) && id.nil?
end
end
@@ -453,13 +427,10 @@ module ActiveRecord
end
def pk_attribute?(name)
- column_for_attribute(name).primary
+ name == self.class.primary_key
end
def typecasted_attribute_value(name)
- # FIXME: we need @attributes to be used consistently.
- # If the values stored in @attributes were already typecasted, this code
- # could be simplified
read_attribute(name)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index f596a8b02e..d057f0941a 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -43,7 +43,9 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- @attributes[attr_name.to_s]
+ if attr = @attributes[attr_name.to_s]
+ attr.value_before_type_cast
+ end
end
# Returns a hash of attributes before typecasting and deserialization.
@@ -57,7 +59,7 @@ module ActiveRecord
# task.attributes_before_type_cast
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
def attributes_before_type_cast
- @attributes
+ @attributes.each_with_object({}) { |(k, v), h| h[k] = v.value_before_type_cast }
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 99070f127b..6a5c057384 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -38,12 +38,39 @@ module ActiveRecord
end
end
- def initialize_dup(other) # :nodoc:
- super
- init_changed_attributes
- end
+ def initialize_dup(other) # :nodoc:
+ super
+ init_changed_attributes
+ end
+
+ def changed?
+ super || changed_in_place.any?
+ end
+
+ def changed
+ super | changed_in_place
+ end
+
+ def attribute_changed?(attr_name, options = {})
+ result = super
+ # We can't change "from" something in place. Only setters can define
+ # "from" and "to"
+ result ||= changed_in_place?(attr_name) unless options.key?(:from)
+ result
+ end
+
+ def changes_applied
+ super
+ store_original_raw_attributes
+ end
+
+ def reset_changes
+ super
+ original_raw_attributes.clear
+ end
+
+ private
- private
def initialize_internals_callback
super
init_changed_attributes
@@ -55,7 +82,7 @@ module ActiveRecord
# optimistic locking) won't get written unless they get marked as changed
self.class.columns.each do |c|
attr, orig_value = c.name, c.default
- changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value)
end
end
@@ -63,19 +90,35 @@ module ActiveRecord
def write_attribute(attr, value)
attr = attr.to_s
- save_changed_attribute(attr, value)
+ old_value = old_attribute_value(attr)
- super(attr, value)
+ result = super
+ store_original_raw_attribute(attr)
+ save_changed_attribute(attr, old_value)
+ result
end
- def save_changed_attribute(attr, value)
- # The attribute already has an unsaved change.
+ def raw_write_attribute(attr, value)
+ attr = attr.to_s
+
+ result = super
+ original_raw_attributes[attr] = value
+ result
+ end
+
+ def save_changed_attribute(attr, old_value)
+ if attribute_changed?(attr)
+ changed_attributes.delete(attr) unless _field_changed?(attr, old_value)
+ else
+ changed_attributes[attr] = old_value if _field_changed?(attr, old_value)
+ end
+ end
+
+ def old_attribute_value(attr)
if attribute_changed?(attr)
- old = changed_attributes[attr]
- changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
+ changed_attributes[attr]
else
- old = clone_attribute_value(:read_attribute, attr)
- changed_attributes[attr] = old if _field_changed?(attr, old, value)
+ clone_attribute_value(:read_attribute, attr)
end
end
@@ -93,34 +136,45 @@ module ActiveRecord
changed
end
- def _field_changed?(attr, old, value)
- if column = column_for_attribute(attr)
- if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
- changes_from_zero_to_string?(old, value))
- value = nil
- else
- value = column.type_cast(value)
- end
+ def _field_changed?(attr, old_value)
+ new_value = read_attribute(attr)
+ raw_value = read_attribute_before_type_cast(attr)
+ column_for_attribute(attr).changed?(old_value, new_value, raw_value)
+ end
+
+ def changed_in_place
+ self.class.attribute_names.select do |attr_name|
+ changed_in_place?(attr_name)
end
+ end
- old != value
+ def changed_in_place?(attr_name)
+ type = type_for_attribute(attr_name)
+ old_value = original_raw_attribute(attr_name)
+ value = read_attribute(attr_name)
+ type.changed_in_place?(old_value, value)
end
- def changes_from_nil_to_empty_string?(column, old, value)
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
- # Hence we don't record it as a change if the value changes from nil to ''.
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
- # be typecast back to 0 (''.to_i => 0)
- column.null && (old.nil? || old == 0) && value.blank?
+ def original_raw_attribute(attr_name)
+ original_raw_attributes.fetch(attr_name) do
+ read_attribute_before_type_cast(attr_name)
+ end
+ end
+
+ def original_raw_attributes
+ @original_raw_attributes ||= {}
end
- def changes_from_zero_to_string?(old, value)
- # For columns with old 0 and value non-empty string
- old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
+ def store_original_raw_attribute(attr_name)
+ type = type_for_attribute(attr_name)
+ value = type.type_cast_for_database(read_attribute(attr_name))
+ original_raw_attributes[attr_name] = value
end
- def non_zero?(value)
- value !~ /\A0+(\.0+)?\z/
+ def store_original_raw_attributes
+ attribute_names.each do |attr|
+ store_original_raw_attribute(attr)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 931209b07b..1c81a5b71b 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -15,8 +15,10 @@ module ActiveRecord
# Returns the primary key value.
def id
- sync_with_transaction_state
- read_attribute(self.class.primary_key)
+ if pk = self.class.primary_key
+ sync_with_transaction_state
+ read_attribute(pk)
+ end
end
# Sets the primary key value.
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index d01e9aea59..8c1cc128f7 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -22,7 +22,7 @@ module ActiveRecord
# the attribute name. Using a constant means that we do not have
# to allocate an object on each call to the attribute method.
# Making it frozen means that it doesn't get duped when used to
- # key the @attributes_cache in read_attribute.
+ # key the @attributes in read_attribute.
def method_body(method_name, const_name)
<<-EOMETHOD
def #{method_name}
@@ -35,35 +35,22 @@ module ActiveRecord
extend ActiveSupport::Concern
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
-
- included do
- class_attribute :attribute_types_cached_by_default, instance_writer: false
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
- end
-
module ClassMethods
- # +cache_attributes+ allows you to declare which converted attribute
- # values should be cached. Usually caching only pays off for attributes
- # with expensive conversion methods, like time related columns (e.g.
- # +created_at+, +updated_at+).
- def cache_attributes(*attribute_names)
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
+ [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
+ define_method method_name do |*|
+ cached_attributes_deprecation_warning(method_name)
+ true
+ end
end
- # Returns the attributes which are cached. By default time related columns
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
- def cached_attributes
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
- end
+ protected
- # Returns +true+ if the provided attribute is being cached.
- def cache_attribute?(attr_name)
- cached_attributes.include?(attr_name)
+ def cached_attributes_deprecation_warning(method_name)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ Calling `#{method_name}` is no longer necessary. All attributes are cached.
+ MESSAGE
end
- protected
-
if Module.methods_transplantable?
def define_method_attribute(name)
method = ReaderMethodCache[name]
@@ -89,45 +76,22 @@ module ActiveRecord
end
end
end
-
- private
-
- def cacheable_column?(column)
- if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
- ! serialized_attributes.include? column.name
- else
- attribute_types_cached_by_default.include?(column.type)
- end
- end
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after
# it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
- # If it's cached, just return it
- # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
name = attr_name.to_s
- @attributes_cache[name] || @attributes_cache.fetch(name) {
- column = @column_types_override[name] if @column_types_override
- column ||= @column_types[name]
-
- return @attributes.fetch(name) {
- if name == 'id' && self.class.primary_key != name
- read_attribute(self.class.primary_key)
- end
- } unless column
-
- value = @attributes.fetch(name) {
- return block_given? ? yield(name) : nil
- }
-
- if self.class.cache_attribute?(name)
- @attributes_cache[name] = column.type_cast(value)
+ @attributes.fetch(name) {
+ if name == 'id'
+ return read_attribute(self.class.primary_key)
+ elsif block_given? && self.class.columns_hash.key?(name)
+ return yield(name)
else
- column.type_cast value
+ return nil
end
- }
+ }.value
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index c3466153d6..734d94865a 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -3,20 +3,7 @@ module ActiveRecord
module Serialization
extend ActiveSupport::Concern
- included do
- # Returns a hash of all the attributes that have been specified for
- # serialization as keys and their class restriction as values.
- class_attribute :serialized_attributes, instance_accessor: false
- self.serialized_attributes = {}
- end
-
module ClassMethods
- ##
- # :method: serialized_attributes
- #
- # Returns a hash of all the attributes that have been specified for
- # serialization as keys and their class restriction as values.
-
# If you have an attribute that needs to be saved to the database as an
# object, and retrieved as the same object, then specify the name of that
# attribute using this method and it will be handled automatically. The
@@ -50,140 +37,27 @@ module ActiveRecord
# serialize :preferences, Hash
# end
def serialize(attr_name, class_name_or_coder = Object)
- include Behavior
-
coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
class_name_or_coder
else
Coders::YAMLColumn.new(class_name_or_coder)
end
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
- # has its own hash of own serialized attributes
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
- end
- end
-
- class Type # :nodoc:
- def initialize(column)
- @column = column
- end
-
- def type_cast(value)
- if value.state == :serialized
- value.unserialized_value @column.type_cast value.value
- else
- value.unserialized_value
- end
- end
-
- def type
- @column.type
- end
-
- def accessor
- ActiveRecord::Store::IndifferentHashAccessor
- end
- end
-
- class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
- def unserialized_value(v = value)
- state == :serialized ? unserialize(v) : value
- end
-
- def serialized_value
- state == :unserialized ? serialize : value
- end
-
- def unserialize(v)
- self.state = :unserialized
- self.value = coder.load(v)
- end
-
- def serialize
- self.state = :serialized
- self.value = coder.dump(value)
- end
- end
-
- # This is only added to the model when serialize is called, which
- # ensures we do not make things slower when serialization is not used.
- module Behavior # :nodoc:
- extend ActiveSupport::Concern
-
- module ClassMethods # :nodoc:
- def initialize_attributes(attributes, options = {})
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
- super(attributes, options)
-
- serialized_attributes.each do |key, coder|
- if attributes.key?(key)
- attributes[key] = Attribute.new(coder, attributes[key], serialized)
- end
- end
-
- attributes
+ decorate_attribute_type(attr_name, :serialize) do |type|
+ Type::Serialized.new(type, coder)
end
end
- def should_record_timestamps?
- super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
- end
-
- def keys_for_partial_write
- super | (attributes.keys & self.class.serialized_attributes.keys)
- end
-
- def type_cast_attribute_for_write(column, value)
- if column && coder = self.class.serialized_attributes[column.name]
- Attribute.new(coder, value, :unserialized)
- else
- super
- end
- end
-
- def _field_changed?(attr, old, value)
- if self.class.serialized_attributes.include?(attr)
- old != value
- else
- super
- end
- end
-
- def read_attribute_before_type_cast(attr_name)
- if self.class.serialized_attributes.include?(attr_name)
- super.unserialized_value
- else
- super
- end
- end
-
- def attributes_before_type_cast
- super.dup.tap do |attributes|
- self.class.serialized_attributes.each_key do |key|
- if attributes.key?(key)
- attributes[key] = attributes[key].unserialized_value
- end
- end
- end
- end
-
- def typecasted_attribute_value(name)
- if self.class.serialized_attributes.include?(name)
- @attributes[name].serialized_value
- else
- super
- end
- end
-
- def attributes_for_coder
- attribute_names.each_with_object({}) do |name, attrs|
- attrs[name] = if self.class.serialized_attributes.include?(name)
- @attributes[name].serialized_value
- else
- read_attribute(name)
- end
- end
+ def serialized_attributes
+ ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc)
+ `serialized_attributes` is deprecated without replacement, and will
+ be removed in Rails 5.0.
+ WARNING
+ @serialized_attributes ||= Hash[
+ columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c|
+ [c.name, c.cast_type.coder]
+ }
+ ]
end
end
end
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..abad949ef4 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,18 +1,27 @@
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
- class Type # :nodoc:
- def initialize(column)
- @column = column
+ class Type < SimpleDelegator # :nodoc:
+ def type_cast_from_database(value)
+ convert_time_to_time_zone(super)
end
- def type_cast(value)
- value = @column.type_cast(value)
- value.acts_like?(:time) ? value.in_time_zone : value
+ def type_cast_from_user(value)
+ if value.is_a?(Array)
+ value.map { |v| type_cast_from_user(v) }
+ elsif value.respond_to?(:in_time_zone)
+ value.in_time_zone
+ end
end
- def type
- @column.type
+ def convert_time_to_time_zone(value)
+ if value.is_a?(Array)
+ value.map { |v| convert_time_to_time_zone(v) }
+ elsif value.acts_like?(:time)
+ value.in_time_zone
+ else
+ value
+ end
end
end
@@ -27,31 +36,11 @@ module ActiveRecord
end
module ClassMethods
- protected
- # Defined for all +datetime+ and +timestamp+ 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])
- method_body, line = <<-EOV, __LINE__ + 1
- def #{attr_name}=(time)
- time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
- previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
- write_attribute(:#{attr_name}, time)
- #{attr_name}_will_change! if previous_time != time_with_zone
- @attributes_cache["#{attr_name}"] = time_with_zone
- end
- EOV
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
- else
- super
- end
- end
-
private
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/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index c853fc0917..246a2cd8ba 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -26,8 +26,6 @@ module ActiveRecord
protected
if Module.methods_transplantable?
- # See define_method_attribute in read.rb for an explanation of
- # this code.
def define_method_attribute=(name)
method = WriterMethodCache[name]
generated_attribute_methods.module_eval {
@@ -55,24 +53,12 @@ module ActiveRecord
# specified +value+. Empty strings for fixnum and float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
- attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
- @attributes_cache.delete(attr_name)
- column = column_for_attribute(attr_name)
-
- # If we're dealing with a binary column, write the data to the cache
- # so we don't attempt to typecast multiple times.
- if column && column.binary?
- @attributes_cache[attr_name] = value
- end
+ write_attribute_with_type_cast(attr_name, value, true)
+ end
- if column || @attributes.has_key?(attr_name)
- @attributes[attr_name] = type_cast_attribute_for_write(column, value)
- else
- raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
- end
+ def raw_write_attribute(attr_name, value)
+ write_attribute_with_type_cast(attr_name, value, false)
end
- alias_method :raw_write_attribute, :write_attribute
private
# Handle *= for method_missing.
@@ -80,10 +66,22 @@ module ActiveRecord
write_attribute(attribute_name, value)
end
- def type_cast_attribute_for_write(column, value)
- return value unless column
+ def write_attribute_with_type_cast(attr_name, value, should_type_cast)
+ attr_name = attr_name.to_s
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
+ type = type_for_attribute(attr_name)
+
+ unless has_attribute?(attr_name) || self.class.columns_hash.key?(attr_name)
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
+ end
+
+ if should_type_cast
+ @attributes[attr_name] = Attribute.from_user(value, type)
+ else
+ @attributes[attr_name] = Attribute.from_database(value, type)
+ end
- column.type_cast_for_write value
+ value
end
end
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
new file mode 100644
index 0000000000..9c80121d70
--- /dev/null
+++ b/activerecord/lib/active_record/attributes.rb
@@ -0,0 +1,122 @@
+module ActiveRecord
+ module Attributes # :nodoc:
+ extend ActiveSupport::Concern
+
+ Type = ActiveRecord::Type
+
+ included do
+ class_attribute :user_provided_columns, instance_accessor: false # :internal:
+ self.user_provided_columns = {}
+ end
+
+ module ClassMethods
+ # Defines or overrides a attribute on this model. This allows customization of
+ # Active Record's type casting behavior, as well as adding support for user defined
+ # types.
+ #
+ # +name+ The name of the methods to define attribute methods for, and the column which
+ # this will persist to.
+ #
+ # +cast_type+ A type object that contains information about how to type cast the value.
+ # See the examples section for more information.
+ #
+ # ==== Options
+ # The options hash accepts the following options:
+ #
+ # +default+ is the default value that the column should use on a new record.
+ #
+ # ==== Examples
+ #
+ # The type detected by Active Record can be overriden.
+ #
+ # # db/schema.rb
+ # create_table :store_listings, force: true do |t|
+ # t.decimal :price_in_cents
+ # end
+ #
+ # # app/models/store_listing.rb
+ # class StoreListing < ActiveRecord::Base
+ # end
+ #
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
+ #
+ # # before
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
+ #
+ # class StoreListing < ActiveRecord::Base
+ # attribute :price_in_cents, Type::Integer.new
+ # end
+ #
+ # # after
+ # store_listing.price_in_cents # => 10
+ #
+ # Users may also define their own custom types, as long as they respond to the methods
+ # defined on the value type. The `type_cast` method on your type object will be called
+ # with values both from the database, and from your controllers. See
+ # `ActiveRecord::Attributes::Type::Value` for the expected API. It is recommended that your
+ # type objects inherit from an existing type, or the base value type.
+ #
+ # class MoneyType < ActiveRecord::Type::Integer
+ # def type_cast(value)
+ # if value.include?('$')
+ # price_in_dollars = value.gsub(/\$/, '').to_f
+ # price_in_dollars * 100
+ # else
+ # value.to_i
+ # end
+ # end
+ # end
+ #
+ # class StoreListing < ActiveRecord::Base
+ # attribute :price_in_cents, MoneyType.new
+ # end
+ #
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
+ # store_listing.price_in_cents # => 1000
+ def attribute(name, cast_type, options = {})
+ name = name.to_s
+ clear_caches_calculated_from_columns
+ # Assign a new hash to ensure that subclasses do not share a hash
+ self.user_provided_columns = user_provided_columns.merge(name => connection.new_column(name, options[:default], cast_type))
+ end
+
+ # Returns an array of column objects for the table associated with this class.
+ def columns
+ @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name))
+ end
+
+ # Returns a hash of column objects for the table associated with this class.
+ def columns_hash
+ @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
+ end
+
+ def reset_column_information # :nodoc:
+ super
+ clear_caches_calculated_from_columns
+ end
+
+ private
+
+ def add_user_provided_columns(schema_columns)
+ existing_columns = schema_columns.map do |column|
+ user_provided_columns[column.name] || column
+ end
+
+ existing_column_names = existing_columns.map(&:name)
+ new_columns = user_provided_columns.except(*existing_column_names).values
+
+ existing_columns + new_columns
+ end
+
+ def clear_caches_calculated_from_columns
+ @columns = nil
+ @columns_hash = nil
+ @column_types = nil
+ @column_defaults = nil
+ @raw_column_defaults = nil
+ @column_names = nil
+ @content_columns = nil
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index f149d8f127..b3c3e26c9f 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -147,6 +147,7 @@ module ActiveRecord
private
def define_non_cyclic_method(name, &block)
+ return if method_defined?(name)
define_method(name) do |*args|
result = true; @_already_called ||= {}
# Loop prevention for validation of associations
@@ -179,30 +180,28 @@ module ActiveRecord
validation_method = :"validate_associated_records_for_#{reflection.name}"
collection = reflection.collection?
- unless method_defined?(save_method)
- if collection
- before_save :before_save_collection_association
-
- define_non_cyclic_method(save_method) { save_collection_association(reflection) }
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
- after_create save_method
- after_update save_method
- elsif reflection.macro == :has_one
- define_method(save_method) { save_has_one_association(reflection) }
- # Configures two callbacks instead of a single after_save so that
- # the model may rely on their execution order relative to its
- # own callbacks.
- #
- # For example, given that after_creates run before after_saves, if
- # we configured instead an after_save there would be no way to fire
- # a custom after_create callback after the child association gets
- # created.
- after_create save_method
- after_update save_method
- else
- define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
- before_save save_method
- end
+ if collection
+ before_save :before_save_collection_association
+
+ define_non_cyclic_method(save_method) { save_collection_association(reflection) }
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
+ after_create save_method
+ after_update save_method
+ elsif reflection.has_one?
+ define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
+ # Configures two callbacks instead of a single after_save so that
+ # the model may rely on their execution order relative to its
+ # own callbacks.
+ #
+ # For example, given that after_creates run before after_saves, if
+ # we configured instead an after_save there would be no way to fire
+ # a custom after_create callback after the child association gets
+ # created.
+ after_create save_method
+ after_update save_method
+ else
+ define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
+ before_save save_method
end
if reflection.validate? && !method_defined?(validation_method)
@@ -273,9 +272,11 @@ module ActiveRecord
# go through nested autosave associations that are loaded in memory (without loading
# any new ones), and return true if is changed for autosave
def nested_records_changed_for_autosave?
- self.class.reflect_on_all_autosave_associations.any? do |reflection|
- association = association_instance_get(reflection.name)
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ self.class._reflections.values.any? do |reflection|
+ if reflection.options[:autosave]
+ association = association_instance_get(reflection.name)
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ end
end
end
@@ -304,7 +305,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}"
@@ -380,15 +382,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..e4d0abb8ef 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -15,10 +15,12 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/class/subclasses'
require 'arel'
+require 'active_record/attribute_decorators'
require 'active_record/errors'
require 'active_record/log_subscriber'
require 'active_record/explain_subscriber'
require 'active_record/relation/delegation'
+require 'active_record/attributes'
module ActiveRecord #:nodoc:
# = Active Record
@@ -296,7 +298,6 @@ module ActiveRecord #:nodoc:
include Core
include Persistence
- include NoTouching
include ReadonlyAttributes
include ModelSchema
include Inheritance
@@ -310,17 +311,20 @@ module ActiveRecord #:nodoc:
include Locking::Optimistic
include Locking::Pessimistic
include AttributeMethods
- include Timestamp
include Callbacks
+ include Timestamp
include Associations
include ActiveModel::SecurePassword
include AutosaveAssociation
include NestedAttributes
include Aggregations
include Transactions
+ include NoTouching
include Reflection
include Serialization
include Store
+ include Attributes
+ include AttributeDecorators
end
ActiveSupport.run_load_hooks(:active_record, Base)
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 47f2ad9b10..e8ce00d92b 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
@@ -185,7 +193,7 @@ module ActiveRecord
# * You are creating a nested (savepoint) transaction
#
# The mysql, mysql2 and postgresql adapters support setting the transaction
- # isolation level. However, support is disabled for mysql versions below 5,
+ # isolation level. However, support is disabled for MySQL versions below 5,
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
# which means the isolation level gets persisted outside the transaction.
def transaction(options = {})
@@ -219,6 +227,10 @@ module ActiveRecord
end
end
+ def open_transactions
+ @transaction.number
+ end
+
def current_transaction #:nodoc:
@transaction
end
@@ -326,8 +338,8 @@ module ActiveRecord
end
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
- # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
- # an UPDATE statement, so in the mysql adapters we redefine this to do that.
+ # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
+ # an UPDATE statement, so in the MySQL adapters we redefine this to do that.
def join_to_update(update, select) #:nodoc:
key = update.key
subselect = subquery_for(key, select)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 75501852ed..04ae67234f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -9,34 +9,16 @@ module ActiveRecord
# records are quoted as their primary key
return value.quoted_id if value.respond_to?(:quoted_id)
- case value
- when String, ActiveSupport::Multibyte::Chars
- value = value.to_s
- return "'#{quote_string(value)}'" unless column
-
- case column.type
- when :integer then value.to_i.to_s
- when :float then value.to_f.to_s
- else
- "'#{quote_string(value)}'"
- end
-
- when true, false
- if column && column.type == :integer
- value ? '1' : '0'
- else
- value ? quoted_true : quoted_false
- end
- # BigDecimals need to be put in a non-normalized form and quoted.
- when nil then "NULL"
- when BigDecimal then value.to_s('F')
- when Numeric, ActiveSupport::Duration then value.to_s
- when Date, Time then "'#{quoted_date(value)}'"
- when Symbol then "'#{quote_string(value.to_s)}'"
- when Class then "'#{value.to_s}'"
- else
- "'#{quote_string(YAML.dump(value))}'"
+ # FIXME: The only case we get an object other than nil or a real column
+ # is `SchemaStatements#add_column` with a PG array that has a non-empty default
+ # value. Is this really the only case? Are we missing tests for other types?
+ # We should have a real column object passed (or nil) here, and check for that
+ # instead
+ if column.respond_to?(:type_cast_for_database)
+ value = column.type_cast_for_database(value)
end
+
+ _quote(value)
end
# Cast a +value+ to a type that the database understands. For example,
@@ -47,34 +29,19 @@ module ActiveRecord
return value.id
end
- case value
- when String, ActiveSupport::Multibyte::Chars
- value = value.to_s
- return value unless column
-
- case column.type
- when :integer then value.to_i
- when :float then value.to_f
- else
- value
- end
-
- when true, false
- if column && column.type == :integer
- value ? 1 : 0
- else
- value ? 't' : 'f'
- end
- # BigDecimals need to be put in a non-normalized form and quoted.
- when nil then nil
- when BigDecimal then value.to_s('F')
- when Numeric then value
- when Date, Time then quoted_date(value)
- when Symbol then value.to_s
- else
- to_type = column ? " to #{column.type}" : ""
- raise TypeError, "can't cast #{value.class}#{to_type}"
+ # FIXME: The only case we get an object other than nil or a real column
+ # is `SchemaStatements#add_column` with a PG array that has a non-empty default
+ # value. Is this really the only case? Are we missing tests for other types?
+ # We should have a real column object passed (or nil) here, and check for that
+ # instead
+ if column.respond_to?(:type_cast_for_database)
+ value = column.type_cast_for_database(value)
end
+
+ _type_cast(value)
+ rescue TypeError
+ to_type = column ? " to #{column.type}" : ""
+ raise TypeError, "can't cast #{value.class}#{to_type}"
end
# Quotes a string, escaping any ' (single quote) and \ (backslash)
@@ -109,10 +76,18 @@ module ActiveRecord
"'t'"
end
+ def unquoted_true
+ 't'
+ end
+
def quoted_false
"'f'"
end
+ def unquoted_false
+ 'f'
+ end
+
def quoted_date(value)
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
@@ -124,6 +99,45 @@ module ActiveRecord
value.to_s(:db)
end
+
+ private
+
+ def types_which_need_no_typecasting
+ [nil, Numeric, String]
+ end
+
+ def _quote(value)
+ case value
+ when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data
+ "'#{quote_string(value.to_s)}'"
+ when true then quoted_true
+ when false then quoted_false
+ when nil then "NULL"
+ # BigDecimals need to be put in a non-normalized form and quoted.
+ when BigDecimal then value.to_s('F')
+ when Numeric, ActiveSupport::Duration then value.to_s
+ when Date, Time then "'#{quoted_date(value)}'"
+ when Symbol then "'#{quote_string(value.to_s)}'"
+ when Class then "'#{value.to_s}'"
+ else
+ "'#{quote_string(YAML.dump(value))}'"
+ end
+ end
+
+ def _type_cast(value)
+ case value
+ when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
+ value.to_s
+ when true then unquoted_true
+ when false then unquoted_false
+ # BigDecimals need to be put in a non-normalized form and quoted.
+ when BigDecimal then value.to_s('F')
+ when Date, Time then quoted_date(value)
+ when *types_which_need_no_typecasting
+ value
+ else raise TypeError
+ end
+ end
end
end
end
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..a9b3e9cfb9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -99,9 +99,11 @@ 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
+ # Note: The precision is the total number of significant digits
+ # and the scale is the number of digits that can be stored following
# the decimal point. For example, the number 123.45 has a precision of 5
# and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
# range from -999.99 to 999.99.
@@ -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_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index cdf0cbe218..5a0efe49c7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -1,5 +1,3 @@
-require 'ipaddr'
-
module ActiveRecord
module ConnectionAdapters # :nodoc:
# The goal of this module is to move Adapter specific column
@@ -20,19 +18,12 @@ module ActiveRecord
def prepare_column_options(column, types)
spec = {}
spec[:name] = column.name.inspect
-
- # AR has an optimization which handles zero-scale decimals as integers. This
- # code ensures that the dumper still dumps the column as a decimal.
- spec[:type] = if column.type == :integer && /^(numeric|decimal)/ =~ column.sql_type
- 'decimal'
- else
- column.type.to_s
- end
- spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit] && spec[:type] != 'decimal'
+ spec[:type] = column.type.to_s
+ spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit]
spec[:precision] = column.precision.inspect if column.precision
spec[:scale] = column.scale.inspect if column.scale
spec[:null] = 'false' unless column.null
- spec[:default] = default_string(column.default) if column.has_default?
+ spec[:default] = column.type_cast_for_schema(column.default) if column.has_default?
spec
end
@@ -40,31 +31,6 @@ module ActiveRecord
def migration_keys
[:name, :limit, :precision, :scale, :default, :null]
end
-
- private
-
- def default_string(value)
- case value
- when BigDecimal
- value.to_s
- when Date, DateTime, Time
- "'#{value.to_s(:db)}'"
- when Range
- # infinity dumps as Infinity, which causes uninitialized constant error
- value.inspect.gsub('Infinity', '::Float::INFINITY')
- when IPAddr
- subnet_mask = value.instance_variable_get(:@mask_addr)
-
- # If the subnet mask is equal to /32, don't output it
- if subnet_mask == (2**32 - 1)
- "\"#{value.to_s}\""
- else
- "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\""
- end
- else
- value.inspect
- end
- end
end
end
end
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..22823a8c58 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]) &&
@@ -787,7 +788,7 @@ module ActiveRecord
return option_strings
end
- # Overridden by the mysql adapter for supporting index lengths
+ # Overridden by the MySQL adapter for supporting index lengths
def quoted_columns_for_index(column_names, options = {})
option_strings = Hash[column_names.map {|name| [name, '']}]
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ffd5055dec..cc494a7f40 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,17 +1,23 @@
require 'date'
require 'bigdecimal'
require 'bigdecimal/util'
+require 'active_record/type'
require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache'
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:
extend ActiveSupport::Autoload
- autoload :Column
+ autoload_at 'active_record/connection_adapters/column' do
+ autoload :Column
+ autoload :NullColumn
+ end
autoload :ConnectionSpecification
autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do
@@ -90,6 +96,8 @@ module ActiveRecord
end
end
+ attr_reader :prepared_statements
+
def initialize(connection, logger = nil, pool = nil) #:nodoc:
super()
@@ -103,6 +111,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 +156,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
@@ -305,10 +328,6 @@ module ActiveRecord
@connection
end
- def open_transactions
- @transaction.number
- end
-
def create_savepoint(name = nil)
end
@@ -318,13 +337,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 +360,80 @@ module ActiveRecord
pool.checkin self
end
+ def type_map # :nodoc:
+ @type_map ||= Type::TypeMap.new.tap do |mapping|
+ initialize_type_map(mapping)
+ end
+ end
+
+ def new_column(name, default, cast_type, sql_type = nil, null = true)
+ Column.new(name, default, cast_type, sql_type, null)
+ end
+
protected
+ def lookup_cast_type(sql_type) # :nodoc:
+ type_map.lookup(sql_type)
+ end
+
+ def initialize_type_map(m) # :nodoc:
+ register_class_with_limit m, %r(boolean)i, Type::Boolean
+ register_class_with_limit m, %r(char)i, Type::String
+ register_class_with_limit m, %r(binary)i, Type::Binary
+ register_class_with_limit m, %r(text)i, Type::Text
+ register_class_with_limit m, %r(date)i, Type::Date
+ register_class_with_limit m, %r(time)i, Type::Time
+ register_class_with_limit m, %r(datetime)i, Type::DateTime
+ register_class_with_limit m, %r(float)i, Type::Float
+ register_class_with_limit m, %r(int)i, Type::Integer
+
+ m.alias_type %r(blob)i, 'binary'
+ m.alias_type %r(clob)i, 'text'
+ m.alias_type %r(timestamp)i, 'datetime'
+ m.alias_type %r(numeric)i, 'decimal'
+ m.alias_type %r(number)i, 'decimal'
+ m.alias_type %r(double)i, 'float'
+
+ m.register_type(%r(decimal)i) do |sql_type|
+ scale = extract_scale(sql_type)
+ precision = extract_precision(sql_type)
+
+ if scale == 0
+ # FIXME: Remove this class as well
+ Type::DecimalWithoutScale.new(precision: precision)
+ else
+ Type::Decimal.new(precision: precision, scale: scale)
+ end
+ end
+ end
+
+ def reload_type_map # :nodoc:
+ type_map.clear
+ initialize_type_map(type_map)
+ end
+
+ def register_class_with_limit(mapping, key, klass) # :nodoc:
+ mapping.register_type(key) do |*args|
+ limit = extract_limit(args.last)
+ klass.new(limit: limit)
+ end
+ end
+
+ def extract_scale(sql_type) # :nodoc:
+ case sql_type
+ when /\((\d+)\)/ then 0
+ when /\((\d+)(,(\d+))\)/ then $3.to_i
+ end
+ end
+
+ def extract_precision(sql_type) # :nodoc:
+ $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
+ end
+
+ def extract_limit(sql_type) # :nodoc:
+ $1.to_i if sql_type =~ /\((.*)\)/
+ end
+
def translate_exception_class(e, sql)
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger
@@ -368,7 +460,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 d2ef83b047..a9b97d5919 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -56,21 +56,18 @@ 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)
+ assert_valid_default(default)
end
- def extract_default(default)
- if blob_or_text_column?
- if default.blank?
- null || strict ? nil : ''
- else
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- elsif missing_default_forged_as_empty_string?(default)
+ def default
+ @default ||= if blob_or_text_column?
+ null || strict ? nil : ''
+ elsif missing_default_forged_as_empty_string?(@original_default)
nil
else
super
@@ -78,7 +75,7 @@ module ActiveRecord
end
def has_default?
- return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
super
end
@@ -86,54 +83,12 @@ module ActiveRecord
sql_type =~ /blob/i || type == :text
end
- # Must return the relevant concrete adapter
- def adapter
- raise NotImplementedError
- end
-
def case_sensitive?
collation && !collation.match(/_ci$/)
end
private
- def simplified_type(field_type)
- return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
-
- case field_type
- when /enum/i, /set/i then :string
- when /year/i then :integer
- when /bit/i then :binary
- else
- super
- end
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /^enum\((.+)\)/i
- $1.split(',').map{|enum| enum.strip.length - 2}.max
- when /blob|text/i
- case sql_type
- when /tiny/i
- 255
- when /medium/i
- 16777215
- when /long/i
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
- else
- super # we could return 65535 here, but we leave it undecorated by default
- end
- when /^bigint/i; 8
- when /^int/i; 4
- when /^mediumint/i; 3
- when /^smallint/i; 2
- when /^tinyint/i; 1
- else
- super
- end
- end
-
# MySQL misreports NOT NULL column default when none is given.
# We can't detect this for columns which may have a legitimate ''
# default (string) but we can for others (integer, datetime, boolean,
@@ -144,6 +99,12 @@ module ActiveRecord
def missing_default_forged_as_empty_string?(default)
type != :string && !null && default == ''
end
+
+ def assert_valid_default(default)
+ if blob_or_text_column? && default.present?
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ end
end
##
@@ -173,7 +134,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,20 +143,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
@@ -223,17 +181,6 @@ module ActiveRecord
true
end
- def type_cast(value, column)
- case value
- when TrueClass
- 1
- when FalseClass
- 0
- else
- super
- end
- end
-
# MySQL 4 technically support transaction isolation, but it is affected by a bug
# where the transaction level gets persisted for the whole session:
#
@@ -262,12 +209,11 @@ 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, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
+ 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
+ # Must return the MySQL error number from the exception, if the exception has an
# error number.
def error_number(exception) # :nodoc:
raise NotImplementedError
@@ -275,12 +221,9 @@ module ActiveRecord
# QUOTING ==================================================
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary
- s = value.unpack("H*")[0]
- "x'#{s}'"
- elsif value.kind_of?(BigDecimal)
- value.to_s("F")
+ def _quote(value) # :nodoc:
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
else
super
end
@@ -298,10 +241,18 @@ module ActiveRecord
QUOTED_TRUE
end
+ def unquoted_true
+ 1
+ end
+
def quoted_false
QUOTED_FALSE
end
+ def unquoted_false
+ 0
+ end
+
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -317,6 +268,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) }
@@ -470,7 +426,9 @@ module ActiveRecord
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
field_name = set_field_encoding(field[:Field])
- new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
+ sql_type = field[:Type]
+ cast_type = lookup_cast_type(sql_type)
+ new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
end
end
end
@@ -609,7 +567,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
@@ -643,6 +602,34 @@ module ActiveRecord
protected
+ def initialize_type_map(m) # :nodoc:
+ super
+ m.register_type(%r(enum)i) do |sql_type|
+ limit = sql_type[/^enum\((.+)\)/i, 1]
+ .split(',').map{|enum| enum.strip.length - 2}.max
+ Type::String.new(limit: limit)
+ end
+
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 255)
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 255)
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 16777215)
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 16777215)
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2147483647)
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2147483647)
+ m.register_type %r(^bigint)i, Type::Integer.new(limit: 8)
+ m.register_type %r(^int)i, Type::Integer.new(limit: 4)
+ m.register_type %r(^mediumint)i, Type::Integer.new(limit: 3)
+ m.register_type %r(^smallint)i, Type::Integer.new(limit: 2)
+ m.register_type %r(^tinyint)i, Type::Integer.new(limit: 1)
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
+
+ m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
+ 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)
@@ -712,15 +699,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
@@ -758,30 +743,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..d629fca911 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -13,107 +13,36 @@ 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_accessor :primary, :coder
+ attr_reader :name, :cast_type, :null, :sql_type, :default_function
- alias :encoded? :coder
+ delegate :type, :precision, :scale, :limit, :klass, :accessor,
+ :text?, :number?, :binary?, :changed?,
+ :type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
+ :type_cast_for_schema,
+ 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)
+ @original_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
- end
- end
-
# Returns the human name of the column name.
#
# ===== Examples
@@ -122,177 +51,22 @@ module ActiveRecord
Base.human_attribute_name(@name)
end
- def extract_default(default)
- type_cast(default)
+ def default
+ @default ||= type_cast_from_database(@original_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
+ def with_type(type)
+ dup.tap do |clone|
+ clone.instance_variable_set('@default', nil)
+ clone.instance_variable_set('@cast_type', type)
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
+ end
- private
- def extract_limit(sql_type)
- $1.to_i if sql_type =~ /\((.*)\)/
- end
-
- 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
+ class NullColumn < Column
+ def initialize(name)
+ super name, nil, Type::Value.new
+ 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 b79d1a4458..2fcb085ab2 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -85,7 +85,7 @@ module ActiveRecord
"password" => uri.password,
"port" => uri.port,
"database" => database_from_path,
- "host" => uri.host })
+ "host" => uri.hostname })
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 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..252b82643d 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
@@ -294,126 +262,70 @@ module ActiveRecord
@connection.insert_id
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
+ module Fields # :nodoc:
+ 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 = 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
@@ -447,7 +359,7 @@ module ActiveRecord
end
end
- def execute_and_free(sql, name = nil)
+ def execute_and_free(sql, name = nil) # :nodoc:
result = execute(sql, name)
ret = yield result
result.free
@@ -460,7 +372,7 @@ module ActiveRecord
end
alias :create :insert_sql
- def exec_delete(sql, name, binds)
+ def exec_delete(sql, name, binds) # :nodoc:
affected_rows = 0
exec_query(sql, name, binds) do |n|
@@ -497,7 +409,7 @@ module ActiveRecord
stmt.execute(*type_casted_binds.map { |_, val| val })
rescue Mysql::Error => e
# Older versions of MySQL leave the prepared statement in a bad
- # place when an error occurs. To support older mysql versions, we
+ # place when an error occurs. To support older MySQL versions, we
# need to close the statement and delete the statement from the
# cache.
stmt.close
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..bb54de05c8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -1,41 +1,12 @@
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)
- 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)
- case value
- when /^0x/i
- value[2..-1].hex.to_s(2) # Hexadecimal notation
- else
- value # Bit-string notation
- 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 +16,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 +30,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 +38,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 +53,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,32 +67,6 @@ module ActiveRecord
end
end
- def string_to_cidr(string)
- if string.nil?
- nil
- elsif String === string
- begin
- IPAddr.new(string)
- rescue ArgumentError
- nil
- end
- else
- string
- end
- end
-
- def cidr_to_string(object)
- if IPAddr === object
- "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
- else
- object
- end
- end
-
- def string_to_array(string, oid)
- parse_pg_array(string).map {|val| type_cast_array(oid, val)}
- end
-
private
HstorePair = begin
@@ -154,14 +99,6 @@ module ActiveRecord
"\"#{value}\""
end
end
-
- def type_cast_array(oid, value)
- if ::Array === value
- value.map {|item| type_cast_array(oid, item)}
- else
- oid.type_cast value
- end
- end
end
end
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..847fd4dded 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -1,158 +1,24 @@
+require 'active_record/connection_adapters/postgresql/cast'
+
module ActiveRecord
module ConnectionAdapters
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
- attr_accessor :array
+ extend PostgreSQL::Cast
- def initialize(name, default, oid_type, sql_type = nil, null = true)
- @oid_type = oid_type
- default_value = self.class.extract_value_from_default(default)
+ attr_accessor :array
+ def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil)
if sql_type =~ /\[\]$/
@array = true
- super(name, default_value, sql_type[0..sql_type.length - 3], null)
+ super(name, default, cast_type, sql_type[0..sql_type.length - 3], null)
else
@array = false
- super(name, default_value, sql_type, null)
+ super(name, default, cast_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
- attr_accessor :money_precision
- end
- # :startdoc:
-
- # Extracts the value from a PostgreSQL column default definition.
- def self.extract_value_from_default(default)
- # This is a performance optimization for Ruby 1.9.2 in development.
- # If the value is nil, we return nil straight away without checking
- # the regular expressions. If we check each regular expression,
- # Regexp#=== will call NilClass#to_str, which will trigger
- # method_missing (defined by whiny nil in ActiveSupport) which
- # makes this method very very slow.
- return default unless default
-
- case default
- when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
- $1
- # Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
- $1
- # Character types
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
- $1.gsub(/''/, "'")
- # Binary data types
- when /\A'(.*)'::bytea\z/m
- $1
- # Date/time types
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
- $1
- when /\A'(.*)'::interval\z/
- $1
- # Boolean type
- when 'true'
- true
- when 'false'
- false
- # Geometric types
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
- $1
- # Network address types
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
- $1
- # Bit string types
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
- $1
- # XML type
- when /\A'(.*)'::xml\z/m
- $1
- # Arrays
- when /\A'(.*)'::"?\D+"?\[\]\z/
- $1
- # Hstore
- when /\A'(.*)'::hstore\z/
- $1
- # JSON
- when /\A'(.*)'::json\z/
- $1
- # Object identifier types
- when /\A-?\d+\z/
- $1
- else
- # Anything else is blank, some user type, or some function
- # and we can't know the value of that, so return nil.
- nil
- 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
+ @default_function = default_function
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
-
- private
-
- def has_default_function?(default_value, default)
- !default_value && (%r{\w+\(.*\)} === default)
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- when /^timestamp/i; nil
- else super
- 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'
- self.class.money_precision
- elsif sql_type =~ /timestamp/i
- $1.to_i if sql_type =~ /\((\d+)\)/
- else
- 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 168b08ba75..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)}"
@@ -94,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
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 9e898015a6..33a98b4fcb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,445 +1,33 @@
-require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/postgresql/oid/infinity'
+
+require 'active_record/connection_adapters/postgresql/oid/array'
+require 'active_record/connection_adapters/postgresql/oid/bit'
+require 'active_record/connection_adapters/postgresql/oid/bit_varying'
+require 'active_record/connection_adapters/postgresql/oid/bytea'
+require 'active_record/connection_adapters/postgresql/oid/cidr'
+require 'active_record/connection_adapters/postgresql/oid/date'
+require 'active_record/connection_adapters/postgresql/oid/date_time'
+require 'active_record/connection_adapters/postgresql/oid/decimal'
+require 'active_record/connection_adapters/postgresql/oid/enum'
+require 'active_record/connection_adapters/postgresql/oid/float'
+require 'active_record/connection_adapters/postgresql/oid/hstore'
+require 'active_record/connection_adapters/postgresql/oid/inet'
+require 'active_record/connection_adapters/postgresql/oid/integer'
+require 'active_record/connection_adapters/postgresql/oid/json'
+require 'active_record/connection_adapters/postgresql/oid/money'
+require 'active_record/connection_adapters/postgresql/oid/point'
+require 'active_record/connection_adapters/postgresql/oid/range'
+require 'active_record/connection_adapters/postgresql/oid/specialized_string'
+require 'active_record/connection_adapters/postgresql/oid/time'
+require 'active_record/connection_adapters/postgresql/oid/uuid'
+require 'active_record/connection_adapters/postgresql/oid/vector'
+
+require 'active_record/connection_adapters/postgresql/oid/type_map_initializer'
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
- module OID
- class Type
- def type; end
- def simplified_type(sql_type); type end
-
- 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
- end
- end
-
- class SpecializedString < OID::String
- def type; @type end
-
- def initialize(type)
- @type = type
- end
- end
-
- class Text < OID::String
- def type; :text end
- end
-
- class Bit < Type
- def type; :string end
-
- def type_cast(value)
- if ::String === value
- ConnectionAdapters::PostgreSQLColumn.string_to_bit value
- else
- value
- end
- end
- end
-
- class Bytea < Type
- def type; :binary end
-
- def type_cast(value)
- return if value.nil?
- PGconn.unescape_bytea value
- end
- end
-
- class Money < Type
- def type; :decimal end
-
- def type_cast(value)
- return if value.nil?
- return value unless ::String === value
-
- # Because money output is formatted according to the locale, there are two
- # cases to consider (note the decimal separators):
- # (1) $12,345,678.12
- # (2) $12.345.678,12
- # Negative values are represented as follows:
- # (3) -$2.55
- # (4) ($2.55)
-
- value.sub!(/^\((.+)\)$/, '-\1') # (4)
- case value
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
- value.gsub!(/[^-\d.]/, '')
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
- value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
- end
-
- ConnectionAdapters::Column.value_to_decimal value
- end
- end
-
- class Vector < Type
- attr_reader :delim, :subtype
-
- # +delim+ corresponds to the `typdelim` column in the pg_types
- # table. +subtype+ is derived from the `typelem` column in the
- # pg_types table.
- def initialize(delim, subtype)
- @delim = delim
- @subtype = subtype
- end
-
- # FIXME: this should probably split on +delim+ and use +subtype+
- # to cast the values. Unfortunately, the current Rails behavior
- # is to just return the string.
- def type_cast(value)
- value
- end
- end
-
- class Point < Type
- def type; :string end
-
- def type_cast(value)
- if ::String === value
- ConnectionAdapters::PostgreSQLColumn.string_to_point value
- else
- value
- end
- end
- end
-
- class Array < Type
- def type; @subtype.type end
-
- attr_reader :subtype
- def initialize(subtype)
- @subtype = subtype
- end
-
- def type_cast(value)
- if ::String === value
- ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype
- else
- value
- end
- end
- end
-
- class Range < Type
- attr_reader :subtype
- def simplified_type(sql_type); sql_type.to_sym end
-
- def initialize(subtype)
- @subtype = subtype
- end
-
- def extract_bounds(value)
- from, to = value[1..-2].split(',')
- {
- from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
- to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
- exclude_start: (value[0] == '('),
- exclude_end: (value[-1] == ')')
- }
- end
-
- def infinity?(value)
- value.respond_to?(:infinite?) && value.infinite?
- end
-
- def type_cast_single(value)
- infinity?(value) ? value : @subtype.type_cast(value)
- end
-
- def type_cast(value)
- return if value.nil? || value == 'empty'
- return value if value.is_a?(::Range)
-
- extracted = extract_bounds(value)
- from = type_cast_single extracted[:from]
- to = type_cast_single extracted[:to]
-
- if !infinity?(from) && extracted[:exclude_start]
- if from.respond_to?(:succ)
- from = from.succ
- ActiveSupport::Deprecation.warn <<-MESSAGE
-Excluding the beginning of a Range is only partialy supported through `#succ`.
-This is not reliable and will be removed in the future.
- MESSAGE
- else
- raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
- end
- end
- ::Range.new(from, to, extracted[:exclude_end])
- end
- end
-
- class Integer < Type
- def type; :integer end
-
- def type_cast(value)
- return if value.nil?
-
- ConnectionAdapters::Column.value_to_integer value
- end
- end
-
- class Boolean < Type
- def type; :boolean end
-
- 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
- else
- :timestamp
- 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
- 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
- end
-
- class Float < Type
- def type; :float end
-
- def type_cast(value)
- return if value.nil?
-
- value.to_f
- end
- end
-
- class Decimal < Type
- def type; :decimal end
-
- def type_cast(value)
- return if value.nil?
-
- ConnectionAdapters::Column.value_to_decimal value
- end
-
- def infinity(options = {})
- BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
- end
- end
-
- class Enum < Type
- def type; :enum end
-
- def type_cast(value)
- value.to_s
- end
- end
-
- class Hstore < Type
- 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?
-
- ConnectionAdapters::PostgreSQLColumn.string_to_hstore value
- end
-
- def accessor
- ActiveRecord::Store::StringKeyedHashAccessor
- end
- end
-
- class Cidr < Type
- def type; :cidr end
- def type_cast(value)
- return if value.nil?
-
- ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
- end
- end
- class Inet < Cidr
- def type; :inet end
- end
-
- class Json < Type
- 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?
-
- ConnectionAdapters::PostgreSQLColumn.string_to_json value
- end
-
- def accessor
- ActiveRecord::Store::StringKeyedHashAccessor
- end
- end
-
- class Uuid < Type
- def type; :uuid end
- def type_cast(value)
- value.presence
- end
- end
-
- class TypeMap
- def initialize
- @mapping = {}
- end
-
- def []=(oid, type)
- @mapping[oid] = type
- end
-
- def [](oid)
- @mapping[oid]
- end
-
- def clear
- @mapping.clear
- end
-
- def key?(oid)
- @mapping.key? oid
- 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
- end
-
- @mapping.fetch(ftype) { |oid| yield oid, fmod }
- end
- end
-
- # When the PG adapter connects, the pg_type table is queried. The
- # key of this hash maps to the `typname` column from the table.
- # 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
- }
-
- # Register an OID type named +name+ with a typecasting object in
- # +type+. +name+ should correspond to the `typname` column in
- # the `pg_type` table.
- def self.register_type(name, type)
- NAMES[name] = type
- end
-
- # Alias the +old+ type to the +new+ type.
- def self.alias_type(new, old)
- NAMES[new] = NAMES[old]
- end
-
- # Is +name+ a registered type?
- def self.registered_type?(name)
- NAMES.key? name
- end
-
- register_type 'int2', OID::Integer.new
- alias_type 'int4', 'int2'
- alias_type 'int8', 'int2'
- alias_type 'oid', 'int2'
- 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
- alias_type 'char', 'varchar'
- alias_type 'bpchar', 'varchar'
- register_type 'bool', OID::Boolean.new
- register_type 'bit', OID::Bit.new
- alias_type 'varbit', 'bit'
- register_type 'timestamp', OID::Timestamp.new
- alias_type 'timestamptz', 'timestamp'
- register_type 'date', OID::Date.new
- register_type 'time', OID::Time.new
-
- register_type 'money', OID::Money.new
- register_type 'bytea', OID::Bytea.new
- register_type 'point', OID::Point.new
- register_type 'hstore', OID::Hstore.new
- register_type 'json', OID::Json.new
- register_type 'cidr', OID::Cidr.new
- register_type 'inet', OID::Inet.new
- register_type 'uuid', OID::Uuid.new
- register_type 'xml', SpecializedString.new(:xml)
- register_type 'tsvector', SpecializedString.new(:tsvector)
- register_type 'macaddr', SpecializedString.new(:macaddr)
- register_type 'citext', SpecializedString.new(:citext)
- register_type 'ltree', SpecializedString.new(:ltree)
-
- # FIXME: why are we keeping these types as strings?
- alias_type 'interval', 'varchar'
- alias_type 'path', 'varchar'
- alias_type 'line', 'varchar'
- alias_type 'polygon', 'varchar'
- alias_type 'circle', 'varchar'
- alias_type 'lseg', 'varchar'
- alias_type 'box', 'varchar'
+ module PostgreSQL
+ module OID # :nodoc:
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
new file mode 100644
index 0000000000..4e7d472d97
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Array < Type::Value
+ attr_reader :subtype
+ delegate :type, to: :subtype
+
+ def initialize(subtype)
+ @subtype = subtype
+ end
+
+ def type_cast_from_database(value)
+ if value.is_a?(::String)
+ type_cast_array(parse_pg_array(value), :type_cast_from_database)
+ else
+ super
+ end
+ end
+
+ def type_cast_from_user(value)
+ type_cast_array(value, :type_cast_from_user)
+ end
+
+ # 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
+
+ private
+
+ def type_cast_array(value, method)
+ if value.is_a?(::Array)
+ value.map { |item| type_cast_array(item, method) }
+ else
+ @subtype.public_send(method, value)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
new file mode 100644
index 0000000000..3073f8ff30
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
@@ -0,0 +1,26 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Bit < Type::Value
+ def type
+ :bit
+ end
+
+ def type_cast(value)
+ if ::String === value
+ case value
+ when /^0x/i
+ value[2..-1].hex.to_s(2) # Hexadecimal notation
+ else
+ value # Bit-string notation
+ end
+ else
+ value
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb
new file mode 100644
index 0000000000..054af285bb
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class BitVarying < OID::Bit
+ def type
+ :bit_varying
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
new file mode 100644
index 0000000000..89b203d2b1
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bytea.rb
@@ -0,0 +1,14 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Bytea < Type::Binary
+ def type_cast_from_database(value)
+ return if value.nil?
+ PGconn.unescape_bytea(super)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
new file mode 100644
index 0000000000..534961a414
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -0,0 +1,46 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Cidr < Type::Value
+ def type
+ :cidr
+ end
+
+ def type_cast_for_schema(value)
+ subnet_mask = value.instance_variable_get(:@mask_addr)
+
+ # If the subnet mask is equal to /32, don't output it
+ if subnet_mask == (2**32 - 1)
+ "\"#{value.to_s}\""
+ else
+ "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\""
+ end
+ end
+
+ def type_cast_for_database(value)
+ if IPAddr === value
+ "#{value.to_s}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
+ else
+ value
+ end
+ end
+
+ def cast_value(value)
+ if value.nil?
+ nil
+ elsif String === value
+ begin
+ IPAddr.new(value)
+ rescue ArgumentError
+ nil
+ end
+ else
+ value
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb
new file mode 100644
index 0000000000..3c30ad5fec
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date.rb
@@ -0,0 +1,11 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Date < Type::Date
+ include Infinity
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
new file mode 100644
index 0000000000..34e2276dd1
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/date_time.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class DateTime < Type::DateTime
+ include Infinity
+
+ def cast_value(value)
+ if value.is_a?(::String)
+ case value
+ when 'infinity' then ::Float::INFINITY
+ when '-infinity' then -::Float::INFINITY
+ when / BC$/
+ astronomical_year = format("%04d", -value[/^\d+/].to_i + 1)
+ super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
+ else
+ super
+ end
+ else
+ value
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
new file mode 100644
index 0000000000..ed4b8911d9
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Decimal < Type::Decimal
+ def infinity(options = {})
+ BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
new file mode 100644
index 0000000000..5fed8b0f89
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/enum.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Enum < Type::Value
+ def type
+ :enum
+ end
+
+ def type_cast(value)
+ value.to_s
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
new file mode 100644
index 0000000000..77dd97e140
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
@@ -0,0 +1,20 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Float < Type::Float
+ include Infinity
+
+ def cast_value(value)
+ case value
+ when 'Infinity' then ::Float::INFINITY
+ when '-Infinity' then -::Float::INFINITY
+ when 'NaN' then ::Float::NAN
+ else value.to_f
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
new file mode 100644
index 0000000000..0dd4b65333
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Hstore < Type::Value
+ include Type::Mutable
+
+ def type
+ :hstore
+ end
+
+ def type_cast_from_database(value)
+ ConnectionAdapters::PostgreSQLColumn.string_to_hstore(value)
+ end
+
+ def type_cast_for_database(value)
+ ConnectionAdapters::PostgreSQLColumn.hstore_to_string(value)
+ end
+
+ def accessor
+ ActiveRecord::Store::StringKeyedHashAccessor
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb
new file mode 100644
index 0000000000..7ed8f5f031
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/inet.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Inet < Cidr
+ def type
+ :inet
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb
new file mode 100644
index 0000000000..d438ffa140
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/infinity.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ module Infinity
+ def infinity(options = {})
+ options[:negative] ? -::Float::INFINITY : ::Float::INFINITY
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb
new file mode 100644
index 0000000000..388d3dd9ed
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/integer.rb
@@ -0,0 +1,11 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Integer < Type::Integer
+ include Infinity
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
new file mode 100644
index 0000000000..d1347c7bb5
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
@@ -0,0 +1,27 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Json < Type::Value
+ include Type::Mutable
+
+ def type
+ :json
+ end
+
+ def type_cast_from_database(value)
+ ConnectionAdapters::PostgreSQLColumn.string_to_json(value)
+ end
+
+ def type_cast_for_database(value)
+ ConnectionAdapters::PostgreSQLColumn.json_to_string(value)
+ end
+
+ def accessor
+ ActiveRecord::Store::StringKeyedHashAccessor
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
new file mode 100644
index 0000000000..d25eb256c2
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -0,0 +1,43 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Money < Type::Decimal
+ include Infinity
+
+ class_attribute :precision
+
+ def type
+ :money
+ end
+
+ def scale
+ 2
+ end
+
+ def cast_value(value)
+ return value unless ::String === value
+
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+ # Negative values are represented as follows:
+ # (3) -$2.55
+ # (4) ($2.55)
+
+ value.sub!(/^\((.+)\)$/, '-\1') # (4)
+ case value
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ value.gsub!(/[^-\d.]/, '')
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
+ end
+
+ super(value)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
new file mode 100644
index 0000000000..9007bfb178
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
@@ -0,0 +1,24 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Point < Type::Value
+ def type
+ :point
+ end
+
+ def type_cast(value)
+ if ::String === value
+ if value[0] == '(' && value[-1] == ')'
+ value = value[1...-1]
+ end
+ value.split(',').map{ |v| Float(v) }
+ else
+ value
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
new file mode 100644
index 0000000000..c289ba8980
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -0,0 +1,60 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Range < Type::Value
+ attr_reader :subtype, :type
+
+ def initialize(subtype, type)
+ @subtype = subtype
+ @type = type
+ end
+
+ def extract_bounds(value)
+ from, to = value[1..-2].split(',')
+ {
+ from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
+ to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
+ exclude_start: (value[0] == '('),
+ exclude_end: (value[-1] == ')')
+ }
+ end
+
+ def infinity?(value)
+ value.respond_to?(:infinite?) && value.infinite?
+ end
+
+ def type_cast_for_schema(value)
+ value.inspect.gsub('Infinity', '::Float::INFINITY')
+ end
+
+ def type_cast_single(value)
+ infinity?(value) ? value : @subtype.type_cast_from_database(value)
+ end
+
+ def cast_value(value)
+ return if value == 'empty'
+ return value if value.is_a?(::Range)
+
+ extracted = extract_bounds(value)
+ from = type_cast_single extracted[:from]
+ to = type_cast_single extracted[:to]
+
+ if !infinity?(from) && extracted[:exclude_start]
+ if from.respond_to?(:succ)
+ from = from.succ
+ ActiveSupport::Deprecation.warn <<-MESSAGE
+Excluding the beginning of a Range is only partialy supported through `#succ`.
+This is not reliable and will be removed in the future.
+ MESSAGE
+ else
+ raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
+ end
+ end
+ ::Range.new(from, to, extracted[:exclude_end])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
new file mode 100644
index 0000000000..7b1ca16bc4
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class SpecializedString < Type::String
+ attr_reader :type
+
+ def initialize(type)
+ @type = type
+ end
+
+ def text?
+ false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb
new file mode 100644
index 0000000000..ea1f599b0f
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/time.rb
@@ -0,0 +1,11 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Time < Type::Time
+ include Infinity
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
new file mode 100644
index 0000000000..28f7a4eafb
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb
@@ -0,0 +1,85 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ # 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 run(records)
+ nodes = records.reject { |row| @store.key? row['oid'].to_i }
+ mapped, nodes = nodes.partition { |row| @store.key? 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
+
+ private
+ def register_mapped_type(row)
+ alias_type row['oid'], row['typname']
+ end
+
+ def register_enum_type(row)
+ register row['oid'], OID::Enum.new
+ end
+
+ def register_array_type(row)
+ if subtype = @store.lookup(row['typelem'].to_i)
+ register row['oid'], OID::Array.new(subtype)
+ end
+ end
+
+ 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 = assert_valid_registration(oid, oid_type)
+ @store.register_type(oid, oid_type)
+ end
+
+ def alias_type(oid, target)
+ oid = assert_valid_registration(oid, target)
+ @store.alias_type(oid, target)
+ end
+
+ def assert_valid_registration(oid, oid_type)
+ raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil?
+ oid.to_i
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
new file mode 100644
index 0000000000..0ed5491887
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Uuid < Type::Value
+ def type
+ :uuid
+ end
+
+ def type_cast(value)
+ value.presence
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
new file mode 100644
index 0000000000..2f7d1be197
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/vector.rb
@@ -0,0 +1,26 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Vector < Type::Value
+ attr_reader :delim, :subtype
+
+ # +delim+ corresponds to the `typdelim` column in the pg_types
+ # table. +subtype+ is derived from the `typelem` column in the
+ # pg_types table.
+ def initialize(delim, subtype)
+ @delim = delim
+ @subtype = subtype
+ end
+
+ # FIXME: this should probably split on +delim+ and use +subtype+
+ # to cast the values. Unfortunately, the current Rails behavior
+ # is to just return the string.
+ def type_cast(value)
+ value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 403e37fde9..3cf40e6cd4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
+ module PostgreSQL
module Quoting
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
@@ -44,11 +44,6 @@ module ActiveRecord
when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
- when IPAddr
- case sql_type
- when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
- else super
- end
when Float
if value.infinite? && column.type == :datetime
"'#{value.to_s.downcase}'"
@@ -66,7 +61,6 @@ module ActiveRecord
end
when String
case sql_type
- when 'bytea' then "'#{escape_bytea(value)}'"
when 'xml' then "xml '#{quote_string(value)}'"
when /^bit/
case value
@@ -110,27 +104,12 @@ module ActiveRecord
super(value, column)
end
end
- when String
- if 'bytea' == column.sql_type
- # Return a bind param hash with format as binary.
- # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
- # for more information
- { value: value, format: 1 }
- else
- super(value, column)
- end
when Hash
case column.sql_type
when 'hstore' then PostgreSQLColumn.hstore_to_string(value, array_member)
when 'json' then PostgreSQLColumn.json_to_string(value)
else super(value, column)
end
- when IPAddr
- if %w(inet cidr).include? column.sql_type
- PostgreSQLColumn.cidr_to_string(value)
- else
- super(value, column)
- end
else
super(value, column)
end
@@ -150,14 +129,7 @@ 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)
- else
- table_name, name_part = extract_pg_identifier_from_name(name_part)
- "#{quote_column_name(schema)}.#{quote_column_name(table_name)}"
- end
+ Utils.extract_schema_qualified_name(name.to_s).quoted
end
def quote_table_name_for_assignment(table, attr)
@@ -177,8 +149,9 @@ module ActiveRecord
result = "#{result}.#{sprintf("%06d", value.usec)}"
end
- if value.year < 0
- result = result.sub(/^-/, "") + " BC"
+ if value.year <= 0
+ bce_year = format("%04d", -value.year + 1)
+ result = result.sub(/^-?\d+/, bce_year) + " BC"
end
result
end
@@ -187,8 +160,29 @@ module ActiveRecord
def quote_default_value(value, column) #:nodoc:
if column.type == :uuid && value =~ /\(\)/
value
- else
- quote(value)
+ else
+ quote(value, column)
+ end
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "'#{escape_bytea(value.to_s)}'"
+ else
+ super
+ end
+ end
+
+ def _type_cast(value)
+ if value.is_a?(Type::Binary::Data)
+ # Return a bind param hash with format as binary.
+ # See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
+ # for more information
+ { value: value.to_s, format: 1 }
+ else
+ super
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_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
new file mode 100644
index 0000000000..0867e5ef54
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -0,0 +1,150 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module ColumnMethods
+ def xml(*args)
+ options = args.extract_options!
+ column(args[0], :xml, options)
+ end
+
+ def tsvector(*args)
+ options = args.extract_options!
+ column(args[0], :tsvector, options)
+ end
+
+ def int4range(name, options = {})
+ column(name, :int4range, options)
+ end
+
+ def int8range(name, options = {})
+ column(name, :int8range, options)
+ end
+
+ def tsrange(name, options = {})
+ column(name, :tsrange, options)
+ end
+
+ def tstzrange(name, options = {})
+ column(name, :tstzrange, options)
+ end
+
+ def numrange(name, options = {})
+ column(name, :numrange, options)
+ end
+
+ def daterange(name, options = {})
+ column(name, :daterange, options)
+ end
+
+ def hstore(name, options = {})
+ column(name, :hstore, options)
+ end
+
+ def ltree(name, options = {})
+ column(name, :ltree, options)
+ end
+
+ def inet(name, options = {})
+ column(name, :inet, options)
+ end
+
+ def cidr(name, options = {})
+ column(name, :cidr, options)
+ end
+
+ def macaddr(name, options = {})
+ column(name, :macaddr, options)
+ end
+
+ def uuid(name, options = {})
+ column(name, :uuid, options)
+ end
+
+ def json(name, options = {})
+ column(name, :json, options)
+ end
+
+ def citext(name, options = {})
+ column(name, :citext, options)
+ end
+
+ def point(name, options = {})
+ column(name, :point, options)
+ end
+
+ def bit(name, options)
+ column(name, :bit, options)
+ end
+
+ def bit_varying(name, options)
+ column(name, :bit_varying, options)
+ end
+
+ def money(name, options)
+ column(name, :money, options)
+ end
+ end
+
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
+ attr_accessor :array
+ end
+
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
+ include ColumnMethods
+
+ # Defines the primary key field.
+ # Use of the native PostgreSQL UUID type is supported, and can be used
+ # by defining your tables as such:
+ #
+ # create_table :stuffs, id: :uuid do |t|
+ # t.string :content
+ # t.timestamps
+ # end
+ #
+ # By default, this will use the +uuid_generate_v4()+ function from the
+ # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
+ # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
+ # set the +:default+ option to +nil+:
+ #
+ # create_table :stuffs, id: false do |t|
+ # t.primary_key :id, :uuid, default: nil
+ # t.uuid :foo_id
+ # t.timestamps
+ # end
+ #
+ # You may also pass a different UUID generation function from +uuid-ossp+
+ # or another library.
+ #
+ # Note that setting the UUID primary key default value to +nil+ will
+ # require you to assure that you always provide a UUID value before saving
+ # a record (as primary keys cannot be +nil+). This might be done via the
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
+ def primary_key(name, type = :primary_key, options = {})
+ return super unless type == :uuid
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()')
+ options[:primary_key] = true
+ column name, type, options
+ end
+
+ def column(name, type = nil, options = {})
+ super
+ column = self[name]
+ column.array = options[:array]
+
+ self
+ end
+
+ private
+
+ def create_column_definition(name, type)
+ PostgreSQL::ColumnDefinition.new name, type
+ end
+ end
+
+ class Table < ActiveRecord::ConnectionAdapters::Table
+ include ColumnMethods
+ end
+ end
+ end
+end
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 1dc7a6f0fd..b2aeb3a058 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,18 +1,18 @@
module ActiveRecord
module ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
+ module PostgreSQL
class SchemaCreation < AbstractAdapter::SchemaCreation
private
def visit_AddColumn(o)
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
add_column_options!(sql, column_options(o))
end
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+.
@@ -101,16 +97,16 @@ module ActiveRecord
# If the schema is not specified as part of +name+ then it will only find tables within
# the current schema search path (regardless of permissions to access tables in other schemas)
def table_exists?(name)
- schema, table = Utils.extract_schema_and_table(name.to_s)
- return false unless table
+ name = Utils.extract_schema_qualified_name(name.to_s)
+ return false unless name.identifier
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
- AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
- AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
+ AND c.relname = '#{name.identifier}'
+ AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
SQL
end
@@ -182,13 +178,15 @@ 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 = get_oid_type(oid.to_i, fmod.to_i, column_name)
- PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
+ oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type)
+ default_value = extract_value_from_default(oid, default)
+ default_function = extract_default_function(default_value, default)
+ new_column(column_name, default_value, oid, type, notnull == 'f', default_function)
end
end
- def column_for(table_name, column_name) #:nodoc:
- columns(table_name).detect { |c| c.name == column_name.to_s }
+ def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc:
+ PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function)
end
# Returns the current database name.
@@ -275,9 +273,9 @@ module ActiveRecord
def default_sequence_name(table_name, pk = nil) #:nodoc:
result = serial_sequence(table_name, pk || 'id')
return nil unless result
- result.split('.').last
+ Utils.extract_schema_qualified_name(result)
rescue ActiveRecord::StatementInvalid
- "#{table_name}_#{pk || 'id'}_seq"
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk || 'id'}_seq")
end
def serial_sequence(table, column)
@@ -314,17 +312,19 @@ module ActiveRecord
# First try looking for a sequence with a dependency on the
# given table's primary key.
result = query(<<-end_sql, 'SCHEMA')[0]
- SELECT attr.attname, seq.relname
+ SELECT attr.attname, nsp.nspname, seq.relname
FROM pg_class seq,
pg_attribute attr,
pg_depend dep,
- pg_constraint cons
+ pg_constraint cons,
+ pg_namespace nsp
WHERE seq.oid = dep.objid
AND seq.relkind = 'S'
AND attr.attrelid = dep.refobjid
AND attr.attnum = dep.refobjsubid
AND attr.attrelid = cons.conrelid
AND attr.attnum = cons.conkey[1]
+ AND seq.relnamespace = nsp.oid
AND cons.contype = 'p'
AND dep.classid = 'pg_class'::regclass
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
@@ -332,7 +332,7 @@ module ActiveRecord
if result.nil? or result.empty?
result = query(<<-end_sql, 'SCHEMA')[0]
- SELECT attr.attname,
+ SELECT attr.attname, nsp.nspname,
CASE
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
@@ -344,13 +344,19 @@ module ActiveRecord
JOIN pg_attribute attr ON (t.oid = attrelid)
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
WHERE t.oid = '#{quote_table_name(table)}'::regclass
AND cons.contype = 'p'
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
end_sql
end
- [result.first, result.last]
+ pk = result.shift
+ if result.last
+ [pk, PostgreSQL::Name.new(*result)]
+ else
+ [pk, nil]
+ end
rescue
nil
end
@@ -378,7 +384,7 @@ module ActiveRecord
clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
pk, seq = pk_and_sequence_for(new_name)
- if seq == "#{table_name}_#{pk}_seq"
+ if seq.identifier == "#{table_name}_#{pk}_seq"
new_seq = "#{new_name}_#{pk}_seq"
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
end
@@ -409,6 +415,7 @@ module ActiveRecord
def change_column_default(table_name, column_name, default)
clear_cache!
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
@@ -490,7 +497,7 @@ module ActiveRecord
# Convert Arel node to string
s = s.to_sql unless s.is_a?(String)
# Remove any ASC/DESC modifiers
- s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
+ s.gsub(/\s+(?:ASC|DESC)?\s*(?:NULLS\s+(?:FIRST|LAST)\s*)?/i, '')
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
[super, *order_columns].join(', ')
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..0290bcb48c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb
@@ -0,0 +1,66 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ # Value Object to hold a schema qualified name.
+ # This is usually the name of a PostgreSQL relation but it can also represent
+ # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent
+ # double quoting.
+ class Name # :nodoc:
+ SEPARATOR = "."
+ attr_reader :schema, :identifier
+
+ def initialize(schema, identifier)
+ @schema, @identifier = unquote(schema), unquote(identifier)
+ end
+
+ def to_s
+ parts.join SEPARATOR
+ end
+
+ def quoted
+ parts.map { |p| PGconn.quote_ident(p) }.join SEPARATOR
+ end
+
+ def ==(o)
+ o.class == self.class && o.parts == parts
+ end
+ alias_method :eql?, :==
+
+ def hash
+ parts.hash
+ end
+
+ protected
+ def unquote(part)
+ return unless part
+ part.gsub(/(^"|"$)/,'')
+ end
+
+ def parts
+ @parts ||= [@schema, @identifier].compact
+ end
+ end
+
+ module Utils # :nodoc:
+ extend self
+
+ # Returns an instance of <tt>ActiveRecord::ConnectionAdapters::PostgreSQL::Name</tt>
+ # extracted from +string+.
+ # +schema+ is nil if not specified in +string+.
+ # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+)
+ # +string+ 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_qualified_name(string)
+ table, schema = string.scan(/[^".\s]+|"[^"]*"/)[0..1].reverse
+ PostgreSQL::Name.new(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 1f1bd342d1..71b05cdbae 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,13 +1,15 @@
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_definitions'
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
@@ -72,139 +74,6 @@ module ActiveRecord
# In addition, default connection parameters of libpq can be set per environment variables.
# See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
- class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :array
- end
-
- module ColumnMethods
- def xml(*args)
- options = args.extract_options!
- column(args[0], 'xml', options)
- end
-
- def tsvector(*args)
- options = args.extract_options!
- column(args[0], 'tsvector', options)
- end
-
- def int4range(name, options = {})
- column(name, 'int4range', options)
- end
-
- def int8range(name, options = {})
- column(name, 'int8range', options)
- end
-
- def tsrange(name, options = {})
- column(name, 'tsrange', options)
- end
-
- def tstzrange(name, options = {})
- column(name, 'tstzrange', options)
- end
-
- def numrange(name, options = {})
- column(name, 'numrange', options)
- end
-
- def daterange(name, options = {})
- column(name, 'daterange', options)
- end
-
- def hstore(name, options = {})
- column(name, 'hstore', options)
- end
-
- def ltree(name, options = {})
- column(name, 'ltree', options)
- end
-
- def inet(name, options = {})
- column(name, 'inet', options)
- end
-
- def cidr(name, options = {})
- column(name, 'cidr', options)
- end
-
- def macaddr(name, options = {})
- column(name, 'macaddr', options)
- end
-
- def uuid(name, options = {})
- column(name, 'uuid', options)
- end
-
- def json(name, options = {})
- column(name, 'json', options)
- end
-
- def citext(name, options = {})
- column(name, 'citext', options)
- end
- end
-
- class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
- include ColumnMethods
-
- # Defines the primary key field.
- # Use of the native PostgreSQL UUID type is supported, and can be used
- # by defining your tables as such:
- #
- # create_table :stuffs, id: :uuid do |t|
- # t.string :content
- # t.timestamps
- # end
- #
- # By default, this will use the +uuid_generate_v4()+ function from the
- # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
- # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
- # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
- # set the +:default+ option to +nil+:
- #
- # create_table :stuffs, id: false do |t|
- # t.primary_key :id, :uuid, default: nil
- # t.uuid :foo_id
- # t.timestamps
- # end
- #
- # You may also pass a different UUID generation function from +uuid-ossp+
- # or another library.
- #
- # Note that setting the UUID primary key default value to +nil+ will
- # require you to assure that you always provide a UUID value before saving
- # a record (as primary keys cannot be +nil+). This might be done via the
- # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
- def primary_key(name, type = :primary_key, options = {})
- return super unless type == :uuid
- options[:default] = options.fetch(:default, 'uuid_generate_v4()')
- options[:primary_key] = true
- column name, type, options
- end
-
- def citext(name, options = {})
- column(name, 'citext', options)
- end
-
- def column(name, type = nil, options = {})
- super
- column = self[name]
- column.array = options[:array]
-
- self
- end
-
- private
-
- def create_column_definition(name, type)
- ColumnDefinition.new name, type
- end
- end
-
- class Table < ActiveRecord::ConnectionAdapters::Table
- include ColumnMethods
- end
-
ADAPTER_NAME = 'PostgreSQL'
NATIVE_DATABASE_TYPES = {
@@ -215,7 +84,6 @@ module ActiveRecord
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "timestamp" },
- timestamp: { name: "timestamp" },
time: { name: "time" },
date: { name: "date" },
daterange: { name: "daterange" },
@@ -235,13 +103,19 @@ module ActiveRecord
uuid: { name: "uuid" },
json: { name: "json" },
ltree: { name: "ltree" },
- citext: { name: "citext" }
+ citext: { name: "citext" },
+ point: { name: "point" },
+ bit: { name: "bit" },
+ bit_varying: { name: "bit varying" },
+ money: { name: "money" },
}
- 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,9 +123,13 @@ module ActiveRecord
ADAPTER_NAME
end
+ def schema_creation # :nodoc:
+ PostgreSQL::SchemaCreation.new self
+ end
+
# Adds `:array` option to the default set provided by the
# AbstractAdapter
- def prepare_column_options(column, types)
+ def prepare_column_options(column, types) # :nodoc:
spec = super
spec[:array] = 'true' if column.respond_to?(:array) && column.array
spec[:default] = "\"#{column.default_function}\"" if column.default_function
@@ -338,19 +216,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 +241,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
@@ -474,14 +348,14 @@ module ActiveRecord
if supports_extensions?
res = exec_query "SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled",
'SCHEMA'
- res.column_types['enabled'].type_cast res.rows.first.first
+ res.column_types['enabled'].type_cast_from_database res.rows.first.first
end
end
def extensions
if supports_extensions?
res = exec_query "SELECT extname from pg_extension", "SCHEMA"
- res.rows.map { |r| res.column_types['extname'].type_cast r.first }
+ res.rows.map { |r| res.column_types['extname'].type_cast_from_database r.first }
else
super
end
@@ -498,25 +372,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
@@ -526,7 +381,7 @@ module ActiveRecord
end
def update_table_definition(table_name, base) #:nodoc:
- Table.new(table_name, base)
+ PostgreSQL::Table.new(table_name, base)
end
protected
@@ -555,46 +410,128 @@ module ActiveRecord
private
- def type_map
- @type_map
- end
-
- def get_oid_type(oid, fmod, column_name)
+ def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
if !type_map.key?(oid)
- initialize_type_map(type_map, [oid])
+ load_additional_types(type_map, [oid])
end
- type_map.fetch(oid, fmod) {
+ type_map.fetch(oid, fmod, sql_type) {
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
- type_map[oid] = OID::Identity.new
+ Type::Value.new.tap do |cast_type|
+ type_map.register_type(oid, cast_type)
+ end
}
end
- def reload_type_map
- type_map.clear
- initialize_type_map(type_map)
- end
-
- def add_oid(row, records_by_oid, type_map)
- return type_map if type_map.key? row['type_elem'].to_i
+ def initialize_type_map(m) # :nodoc:
+ register_class_with_limit m, 'int2', OID::Integer
+ m.alias_type 'int4', 'int2'
+ m.alias_type 'int8', 'int2'
+ m.alias_type 'oid', 'int2'
+ m.register_type 'float4', OID::Float.new
+ m.alias_type 'float8', 'float4'
+ m.register_type 'text', Type::Text.new
+ register_class_with_limit m, 'varchar', Type::String
+ m.alias_type 'char', 'varchar'
+ m.alias_type 'name', 'varchar'
+ m.alias_type 'bpchar', 'varchar'
+ m.register_type 'bool', Type::Boolean.new
+ register_class_with_limit m, 'bit', OID::Bit
+ register_class_with_limit m, 'varbit', OID::BitVarying
+ m.alias_type 'timestamptz', 'timestamp'
+ m.register_type 'date', OID::Date.new
+ m.register_type 'time', OID::Time.new
+
+ m.register_type 'money', OID::Money.new
+ m.register_type 'bytea', OID::Bytea.new
+ m.register_type 'point', OID::Point.new
+ m.register_type 'hstore', OID::Hstore.new
+ m.register_type 'json', OID::Json.new
+ m.register_type 'cidr', OID::Cidr.new
+ m.register_type 'inet', OID::Inet.new
+ m.register_type 'uuid', OID::Uuid.new
+ m.register_type 'xml', OID::SpecializedString.new(:xml)
+ m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
+ m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
+ m.register_type 'citext', OID::SpecializedString.new(:citext)
+ m.register_type 'ltree', OID::SpecializedString.new(:ltree)
+
+ # FIXME: why are we keeping these types as strings?
+ m.alias_type 'interval', 'varchar'
+ m.alias_type 'path', 'varchar'
+ m.alias_type 'line', 'varchar'
+ m.alias_type 'polygon', 'varchar'
+ m.alias_type 'circle', 'varchar'
+ m.alias_type 'lseg', 'varchar'
+ m.alias_type 'box', 'varchar'
+
+ m.register_type 'timestamp' do |_, _, sql_type|
+ precision = extract_precision(sql_type)
+ OID::DateTime.new(precision: precision)
+ end
- if OID.registered_type? row['typname']
- # this composite type is explicitly registered
- vector = OID::NAMES[row['typname']]
- 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
+ m.register_type 'numeric' do |_, fmod, sql_type|
+ precision = extract_precision(sql_type)
+ scale = extract_scale(sql_type)
+
+ # 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 fmod && (fmod - 4 & 0xffff).zero?
+ # FIXME: Remove this class, and the second argument to
+ # lookups on PG
+ Type::DecimalWithoutScale.new(precision: precision)
+ else
+ OID::Decimal.new(precision: precision, scale: scale)
end
+ end
+
+ load_additional_types(m)
+ end
- vector = OID::Vector.new row['typdelim'], type_map[row['typelem'].to_i]
+ def extract_limit(sql_type) # :nodoc:
+ case sql_type
+ when /^bigint/i; 8
+ when /^smallint/i; 2
+ else super
end
+ end
+
+ # Extracts the value from a PostgreSQL column default definition.
+ def extract_value_from_default(oid, default) # :nodoc:
+ case default
+ # Quoted types
+ when /\A[\(B]?'(.*)'::/m
+ $1.gsub(/''/, "'")
+ # Boolean types
+ when 'true', 'false'
+ default
+ # Numeric types
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
+ $1
+ # Object identifier types
+ when /\A-?\d+\z/
+ $1
+ else
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
+ nil
+ end
+ end
+
+ def extract_default_function(default_value, default) # :nodoc:
+ default if has_default_function?(default_value, default)
+ end
- type_map[row['oid'].to_i] = vector
- type_map
+ def has_default_function?(default_value, default) # :nodoc:
+ !default_value && (%r{\w+\(.*\)} === default)
end
- def initialize_type_map(type_map, oids = nil)
+ def load_additional_types(type_map, oids = nil) # :nodoc:
if supports_ranges?
query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
@@ -612,52 +549,9 @@ module ActiveRecord
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
end
- result = execute(query, 'SCHEMA')
- 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
- 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:
@@ -729,11 +623,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
@@ -742,7 +631,7 @@ module ActiveRecord
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
# should know about this but can't detect it there, so deal with it here.
- PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10
+ OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10
configure_connection
rescue ::PG::Error => error
@@ -824,7 +713,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
@@ -836,23 +725,13 @@ module ActiveRecord
end_sql
end
- def extract_pg_identifier_from_name(name)
- match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/)
-
- if match_data
- rest = name[match_data[0].length, name.length]
- rest = rest[1, rest.length] if rest.start_with? "."
- [match_data[1], (rest.length > 0 ? rest : nil)]
- 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)
- TableDefinition.new native_database_types, name, temporary, options, as
+ def create_table_definition(name, temporary, options, as = nil) # :nodoc:
+ PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index e5c9f6f54a..4d8afcf16a 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -12,11 +12,10 @@ module ActiveRecord
@columns_hash = {}
@primary_keys = {}
@tables = {}
- prepare_default_proc
end
def primary_keys(table_name)
- @primary_keys[table_name]
+ @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
end
# A cached lookup for table existence.
@@ -29,9 +28,9 @@ module ActiveRecord
# Add internal cache for table with +table_name+.
def add(table_name)
if table_exists?(table_name)
- @primary_keys[table_name]
- @columns[table_name]
- @columns_hash[table_name]
+ primary_keys(table_name)
+ columns(table_name)
+ columns_hash(table_name)
end
end
@@ -40,14 +39,16 @@ module ActiveRecord
end
# Get the columns for a table
- def columns(table)
- @columns[table]
+ def columns(table_name)
+ @columns[table_name] ||= connection.columns(table_name)
end
# Get the columns for a table as a hash, key is the column name
# value is the column object.
- def columns_hash(table)
- @columns_hash[table]
+ def columns_hash(table_name)
+ @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
+ [col.name, col]
+ }]
end
# Clears out internal caches
@@ -76,32 +77,11 @@ module ActiveRecord
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
@version = ActiveRecord::Migrator.current_version
- [@version] + [@columns, @columns_hash, @primary_keys, @tables].map { |val|
- Hash[val]
- }
+ [@version, @columns, @columns_hash, @primary_keys, @tables]
end
def marshal_load(array)
@version, @columns, @columns_hash, @primary_keys, @tables = array
- prepare_default_proc
- end
-
- private
-
- def prepare_default_proc
- @columns.default_proc = Proc.new do |h, table_name|
- h[table_name] = connection.columns(table_name)
- end
-
- @columns_hash.default_proc = Proc.new do |h, table_name|
- h[table_name] = Hash[columns(table_name).map { |col|
- [col.name, col]
- }]
- end
-
- @primary_keys.default_proc = Proc.new do |h, table_name|
- h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
- end
end
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 2d5c47967d..e6163771e8 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -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,10 +120,6 @@ module ActiveRecord
end
end
- class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
- include Arel::Visitors::BindVisitor
- end
-
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@@ -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
@@ -225,10 +219,9 @@ module ActiveRecord
# QUOTING ==================================================
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary
- s = value.unpack("H*")[0]
- "x'#{s}'"
+ def _quote(value) # :nodoc:
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
else
super
end
@@ -273,7 +266,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 +292,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 +390,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)
+ new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
end
end
@@ -495,14 +493,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/core.rb b/activerecord/lib/active_record/core.rb
index d6df98a80f..d39e5fddfe 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
@@ -183,16 +249,19 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil, options = {})
- defaults = self.class.column_defaults.dup
- defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
+ defaults = {}
+ self.class.raw_column_defaults.each do |k, v|
+ default = v.duplicable? ? v.dup : v
+ defaults[k] = Attribute.from_database(default, type_for_attribute(k))
+ end
- @attributes = self.class.initialize_attributes(defaults)
- @column_types_override = nil
+ @attributes = defaults
@column_types = self.class.column_types
init_internals
initialize_internals_callback
+ self.class.define_attribute_methods
# +options+ argument is only needed to make protected_attributes gem easier to hook.
# Remove it when we drop support to this gem.
init_attributes(attributes, options) if attributes
@@ -212,13 +281,14 @@ module ActiveRecord
# post.init_with('attributes' => { 'title' => 'hello world' })
# post.title # => 'hello world'
def init_with(coder)
- @attributes = self.class.initialize_attributes(coder['attributes'])
- @column_types_override = coder['column_types']
+ @attributes = coder['attributes']
@column_types = self.class.column_types
init_internals
- @new_record = false
+ @new_record = coder['new_record']
+
+ self.class.define_attribute_methods
run_callbacks :find
run_callbacks :initialize
@@ -254,17 +324,14 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
- cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
- self.class.initialize_attributes(cloned_attributes, :serialized => false)
-
- @attributes = cloned_attributes
- @attributes[self.class.primary_key] = nil
+ pk = self.class.primary_key
+ @attributes = other.clone_attributes
+ @attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@aggregation_cache = {}
@association_cache = {}
- @attributes_cache = {}
@new_record = true
@destroyed = false
@@ -285,7 +352,10 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- coder['attributes'] = attributes_for_coder
+ # FIXME: Remove this when we better serialize attributes
+ coder['raw_attributes'] = attributes_before_type_cast
+ coder['attributes'] = @attributes
+ coder['new_record'] = new_record?
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -308,7 +378,11 @@ module ActiveRecord
# Delegates to id in order to allow two records of the same type and id to work with something like:
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
- id.hash
+ if id
+ id.hash
+ else
+ super
+ end
end
# Clone and freeze the attributes hash such that associations are still
@@ -364,6 +438,29 @@ module ActiveRecord
"#<#{self.class} #{inspection}>"
end
+ # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
+ # when pp is required.
+ def pretty_print(pp)
+ pp.object_address_group(self) do
+ if defined?(@attributes) && @attributes
+ column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
+ pp.seplist(column_names, proc { pp.text ',' }) do |column_name|
+ column_value = read_attribute(column_name)
+ pp.breakable ' '
+ pp.group(1) do
+ pp.text column_name
+ pp.text ':'
+ pp.breakable
+ pp.pp column_value
+ end
+ end
+ else
+ pp.breakable ' '
+ pp.text 'not initialized'
+ end
+ end
+ end
+
# Returns a hash of the given methods with their names as keys and returned values as values.
def slice(*methods)
Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
@@ -428,11 +525,10 @@ module ActiveRecord
def init_internals
pk = self.class.primary_key
- @attributes[pk] = nil unless @attributes.key?(pk)
+ @attributes[pk] ||= Attribute.from_database(nil, type_for_attribute(pk))
@aggregation_cache = {}
@association_cache = {}
- @attributes_cache = {}
@readonly = false
@destroyed = false
@marked_for_destruction = false
@@ -452,5 +548,11 @@ module ActiveRecord
def init_attributes(attributes, options)
assign_attributes(attributes)
end
+
+ def thaw
+ if frozen?
+ @attributes = @attributes.dup
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index b7b790322a..05c4b13016 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
@@ -29,12 +34,11 @@ module ActiveRecord
foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
+ reflection = child_class._reflections.values.find { |e| :belongs_to == e.macro && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
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
@@ -162,7 +166,7 @@ module ActiveRecord
end
def each_counter_cached_associations
- reflections.each do |name, reflection|
+ _reflections.each do |name, reflection|
yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
end
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 18f1ca26de..38c6dcf88d 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -27,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:
#
@@ -138,17 +140,14 @@ module ActiveRecord
@_enum_methods_module ||= begin
mod = Module.new do
private
- def save_changed_attribute(attr_name, value)
+ def save_changed_attribute(attr_name, old)
if (mapping = self.class.defined_enums[attr_name.to_s])
+ value = read_attribute(attr_name)
if attribute_changed?(attr_name)
- old = changed_attributes[attr_name]
-
if mapping[old] == value
changed_attributes.delete(attr_name)
end
else
- old = clone_attribute_value(:read_attribute, attr_name)
-
if old != value
changed_attributes[attr_name] = mapping.key old
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 71efbb8f93..52c70977ef 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -30,17 +30,18 @@ module ActiveRecord
class SerializationTypeMismatch < ActiveRecordError
end
- # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
- # misses adapter field).
+ # Raised when adapter not specified on connection (or configuration file
+ # +config/database.yml+ misses adapter field).
class AdapterNotSpecified < ActiveRecordError
end
- # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
+ # Raised when Active Record cannot find database adapter specified in
+ # +config/database.yml+ or programmatically.
class AdapterNotFound < ActiveRecordError
end
- # Raised when connection to the database could not been established (for example when <tt>connection=</tt>
- # is given a nil object).
+ # Raised when connection to the database could not been established (for
+ # example when +connection=+ is given a nil object).
class ConnectionNotEstablished < ActiveRecordError
end
@@ -82,19 +83,17 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
- # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example,
- # when using +find+ method)
- # does not match number of expected variables.
+ # Raised when number of bind variables in statement given to +:condition+ key
+ # (for example, when using +find+ method) does not match number of expected
+ # values supplied.
#
- # For example, in
+ # For example, when there are two placeholders with only one value supplied:
#
# Location.where("lat = ? AND lng = ?", 53.7362)
- #
- # two placeholders are given but only one variable to fill them.
class PreparedStatementInvalid < ActiveRecordError
end
- # Raised when a given database does not exist
+ # Raised when a given database does not exist.
class NoDatabaseError < StatementInvalid
end
@@ -102,7 +101,8 @@ module ActiveRecord
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
#
- # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
+ # Read more about optimistic locking in ActiveRecord::Locking module
+ # documentation.
class StaleObjectError < ActiveRecordError
attr_reader :record, :attempted_action
@@ -114,8 +114,9 @@ module ActiveRecord
end
- # Raised when association is being configured improperly or
- # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
+ # Raised when association is being configured improperly or user tries to use
+ # offset and limit together with +has_many+ or +has_and_belongs_to_many+
+ # associations.
class ConfigurationError < ActiveRecordError
end
@@ -153,7 +154,8 @@ module ActiveRecord
class Rollback < ActiveRecordError
end
- # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
+ # Raised when attribute has a name reserved by Active Record (when attribute
+ # has name of one of Active Record instance methods).
class DangerousAttributeError < ActiveRecordError
end
@@ -171,7 +173,7 @@ module ActiveRecord
end
# Raised when an error occurred while doing a mass assignment to an attribute through the
- # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
+ # +attributes=+ method. The exception has an +attribute+ property that is the name of the
# offending attribute.
class AttributeAssignmentError < ActiveRecordError
attr_reader :exception, :attribute
@@ -217,6 +219,13 @@ module ActiveRecord
class ImmutableRelation < ActiveRecordError
end
+ # TransactionIsolationError will be raised under the following conditions:
+ #
+ # * The adapter does not support setting the isolation level
+ # * You are joining an existing open transaction
+ # * You are creating a nested (savepoint) transaction
+ #
+ # The mysql, mysql2 and postgresql adapters support setting the transaction isolation level.
class TransactionIsolationError < ActiveRecordError
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 47d32fae05..4cd5f92207 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -15,9 +15,10 @@ module ActiveRecord
# They are stored in YAML files, one file per model, which are placed in the directory
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
- # The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
- # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a fixture file looks
- # like this:
+ # The fixture file ends with the +.yml+ file extension, for example:
+ # <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
+ #
+ # The format of a fixture file looks like this:
#
# rubyonrails:
# id: 1
@@ -33,7 +34,7 @@ module ActiveRecord
# is followed by an indented list of key/value pairs in the "key: value" format. Records are
# separated by a blank line for your viewing pleasure.
#
- # Note that fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
+ # Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
# See http://yaml.org/type/omap.html
# for the specification. You will need ordered fixtures when you have foreign key constraints
# on keys in the same table. This is commonly needed for tree structures. Example:
@@ -61,8 +62,8 @@ module ActiveRecord
# end
# end
#
- # By default, <tt>test_helper.rb</tt> will load all of your fixtures into your test database,
- # so this test will succeed.
+ # By default, +test_helper.rb+ will load all of your fixtures into your test
+ # database, so this test will succeed.
#
# The testing environment will automatically load the all fixtures into the database before each
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
@@ -374,8 +375,9 @@ module ActiveRecord
#
# == Support for YAML defaults
#
- # You probably already know how to use YAML to set and reuse defaults in
- # your <tt>database.yml</tt> file. You can use the same technique in your fixtures:
+ # You can set and reuse defaults in your fixtures YAML file.
+ # This is the same technique used in the +database.yml+ file to specify
+ # defaults:
#
# DEFAULTS: &DEFAULTS
# created_on: <%= 3.weeks.ago.to_s(:db) %>
@@ -391,7 +393,8 @@ module ActiveRecord
# Any fixture labeled "DEFAULTS" is safely ignored.
class FixtureSet
#--
- # An instance of FixtureSet is normally stored in a single YAML file and possibly in a folder with the same name.
+ # An instance of FixtureSet is normally stored in a single YAML file and
+ # possibly in a folder with the same name.
#++
MAX_ID = 2 ** 30 - 1
@@ -461,13 +464,7 @@ module ActiveRecord
@config = config
# Remove string values that aren't constants or subclasses of AR
- @class_names.delete_if { |k,klass|
- unless klass.is_a? Class
- klass = klass.safe_constantize
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `set_fixture_class` will be removed in Rails 4.2. Use the class itself instead.")
- end
- !insert_class(@class_names, k, klass)
- }
+ @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) }
end
def [](fs_name)
@@ -573,10 +570,6 @@ module ActiveRecord
@config = config
@model_class = nil
- if class_name.is_a?(String)
- ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name to `FixtureSet.new` will be removed in Rails 4.2. Use the class itself instead.")
- end
-
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
@model_class = class_name
else
@@ -649,14 +642,14 @@ module ActiveRecord
model_class
end
- reflection_class.reflect_on_all_associations.each do |association|
+ reflection_class._reflections.values.each do |association|
case association.macro
when :belongs_to
# Do not replace association name with association foreign key if they are named the same
fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
# support polymorphic belongs_to as "label (Type)"
row[association.foreign_type] = $1
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 4d63b04d9f..4528d8783c 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -66,7 +66,7 @@ module ActiveRecord
send(lock_col + '=', previous_lock_value + 1)
end
- def _update_record(attribute_names = @attributes.keys) #:nodoc:
+ def _update_record(attribute_names = self.attribute_names) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
@@ -151,12 +151,6 @@ module ActiveRecord
@locking_column
end
- # Quote the column name used for optimistic locking.
- def quoted_locking_column
- ActiveSupport::Deprecation.warn "ActiveRecord::Base.quoted_locking_column is deprecated and will be removed in Rails 4.2 or later."
- connection.quote_column_name(locking_column)
- end
-
# Reset the column used for optimistic locking back to the +lock_version+ default.
def reset_locking_column
self.locking_column = DEFAULT_LOCKING_COLUMN
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 d0bc4c0af9..01c001e692 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
#
@@ -643,7 +643,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
@@ -714,7 +714,7 @@ module ActiveRecord
if ActiveRecord::Base.timestamped_migrations
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
else
- "%.3d" % number
+ SchemaMigration.normalize_migration_number(number)
end
end
@@ -854,19 +854,6 @@ module ActiveRecord
migrations(migrations_paths).last || NullMigration.new
end
- def proper_table_name(name, options = {})
- ActiveSupport::Deprecation.warn "ActiveRecord::Migrator.proper_table_name is deprecated and will be removed in Rails 4.2. Use the proper_table_name instance method on ActiveRecord::Migration instead"
- options = {
- table_name_prefix: ActiveRecord::Base.table_name_prefix,
- table_name_suffix: ActiveRecord::Base.table_name_suffix
- }.merge(options)
- if name.respond_to? :table_name
- name.table_name
- else
- "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}"
- end
- end
-
def migrations_paths
@migrations_paths ||= ['db/migrate']
# just to not break things if someone uses: migration_path = some_string
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index dc5ff02882..9e1afd32e6 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 = ""
@@ -47,6 +51,8 @@ module ActiveRecord
self.pluralize_table_names = true
self.inheritance_column = 'type'
+
+ delegate :type_for_attribute, to: :class
end
module ClassMethods
@@ -153,6 +159,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 +200,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
@@ -209,44 +219,26 @@ module ActiveRecord
connection.schema_cache.table_exists?(table_name)
end
- # Returns an array of column objects for the table associated with this class.
- def columns
- @columns ||= connection.schema_cache.columns(table_name).map do |col|
- col = col.dup
- col.primary = (col.name == primary_key)
- col
- end
- end
-
- # Returns a hash of column objects for the table associated with this class.
- def columns_hash
- @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
- end
-
def column_types # :nodoc:
- @column_types ||= decorate_columns(columns_hash.dup)
+ @column_types ||= decorate_types(build_types_hash)
end
- def decorate_columns(columns_hash) # :nodoc:
- return if columns_hash.empty?
-
- @serialized_column_names ||= self.columns_hash.keys.find_all do |name|
- serialized_attributes.key?(name)
- end
+ def type_for_attribute(attr_name) # :nodoc:
+ column_types.fetch(attr_name) { Type::Value.new }
+ end
- @serialized_column_names.each do |name|
- columns_hash[name] = AttributeMethods::Serialization::Type.new(columns_hash[name])
- end
+ def decorate_types(types) # :nodoc:
+ return if types.empty?
@time_zone_column_names ||= self.columns_hash.find_all do |name, col|
create_time_zone_conversion_attribute?(name, col)
end.map!(&:first)
@time_zone_column_names.each do |name|
- columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(columns_hash[name])
+ types[name] = AttributeMethods::TimeZoneConversion::Type.new(types[name])
end
- columns_hash
+ types
end
# Returns a hash where the keys are column names and the values are
@@ -255,6 +247,14 @@ module ActiveRecord
@column_defaults ||= Hash[columns.map { |c| [c.name, c.default] }]
end
+ # Returns a hash where the keys are the column names and the values
+ # are the default values suitable for use in `@raw_attriubtes`
+ def raw_column_defaults # :nodoc:
+ @raw_column_defauts ||= Hash[column_defaults.map { |name, default|
+ [name, columns_hash[name].type_cast_for_database(default)]
+ }]
+ end
+
# Returns an array of column names as strings.
def column_names
@column_names ||= columns.map { |column| column.name }
@@ -263,7 +263,7 @@ module ActiveRecord
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
# and columns used for single table inheritance have been removed.
def content_columns
- @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
+ @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
end
# Resets all the cached information about columns, which will cause them
@@ -299,26 +299,17 @@ module ActiveRecord
@arel_engine = nil
@column_defaults = nil
+ @raw_column_defauts = nil
@column_names = nil
- @columns = nil
- @columns_hash = nil
@column_types = nil
@content_columns = nil
@dynamic_methods_hash = nil
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
@relation = nil
- @serialized_column_names = nil
@time_zone_column_names = nil
@cached_time_zone = nil
end
- # This is a hook for use by modules that need to do extra stuff to
- # attributes when they are initialized. (e.g. attribute
- # serialization)
- def initialize_attributes(attributes, options = {}) #:nodoc:
- attributes
- end
-
private
# Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -337,12 +328,17 @@ 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
end
end
+
+ def build_types_hash
+ Hash[columns.map { |column| [column.name, column.cast_type] }]
+ end
end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index e6195e48a5..8a2a06f2ca 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -305,7 +305,7 @@ module ActiveRecord
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
- if reflection = reflect_on_association(association_name)
+ if reflection = _reflect_on_association(association_name)
reflection.autosave = true
add_autosave_association_callbacks(reflection)
@@ -516,7 +516,7 @@ module ActiveRecord
# Determines if a hash contains a truthy _destroy key.
def has_destroy_flag?(hash)
- ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
+ Type::Boolean.new.type_cast_from_user(hash['_destroy'])
end
# Determines if a new record should be rejected by checking
@@ -542,7 +542,7 @@ module ActiveRecord
end
def raise_nested_attributes_record_not_found!(association_name, record_id)
- raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
+ raise RecordNotFound, "Couldn't find #{self.class._reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 05d0c41678..807c301596 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -23,7 +23,7 @@ module ActiveRecord
end
def size
- 0
+ calculate :size, nil
end
def empty?
@@ -47,14 +47,28 @@ module ActiveRecord
end
def sum(*)
- 0
+ calculate :sum, nil
+ end
+
+ def average(*)
+ calculate :average, nil
+ end
+
+ def minimum(*)
+ calculate :minimum, nil
+ end
+
+ def maximum(*)
+ calculate :maximum, nil
end
def calculate(operation, _column_name, _options = {})
# TODO: Remove _options argument as soon we remove support to
# activerecord-deprecated_finders.
- if operation == :count
+ if [:count, :sum, :size].include? operation
group_values.any? ? Hash.new : 0
+ elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
+ Hash.new
else
nil
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 13d7432773..5c744762d9 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,14 @@ 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)
- column_types = klass.decorate_columns(column_types.dup)
- klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
+ def instantiate(attributes, column_types = {})
+ klass = discriminate_class_for_record(attributes)
+
+ attributes = attributes.each_with_object({}) do |(name, value), h|
+ type = column_types.fetch(name) { klass.type_for_attribute(name) }
+ h[name] = Attribute.from_database(value, type)
+ end
+ klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
end
private
@@ -180,7 +184,6 @@ module ActiveRecord
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@attributes_cache", @attributes_cache)
became.instance_variable_set("@changed_attributes", @changed_attributes) if defined?(@changed_attributes)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
@@ -398,9 +401,7 @@ module ActiveRecord
@attributes.update(fresh_object.instance_variable_get('@attributes'))
- @column_types = self.class.column_types
- @column_types_override = fresh_object.instance_variable_get('@column_types_override')
- @attributes_cache = {}
+ @column_types = self.class.column_types
self
end
@@ -490,7 +491,7 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
- def _update_record(attribute_names = @attributes.keys)
+ def _update_record(attribute_names = self.attribute_names)
attributes_values = arel_attributes_with_values_for_update(attribute_names)
if attributes_values.empty?
0
@@ -501,7 +502,7 @@ module ActiveRecord
# Creates a record with values matching those of the instance attributes
# and returns its id.
- def _create_record(attribute_names = @attributes.keys)
+ def _create_record(attribute_names = self.attribute_names)
attributes_values = arel_attributes_with_values_for_create(attribute_names)
new_id = self.class.unscoped.insert attributes_values
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index ef138c6f80..39817703cd 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -40,7 +40,7 @@ module ActiveRecord
column_types = {}
if result_set.respond_to? :column_types
- column_types = result_set.column_types
+ column_types = result_set.column_types.except(*columns_hash.keys)
else
ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 6b0459ea37..6dca206f2a 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -83,18 +83,18 @@ 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 }
+ db_list.map! { |version| ActiveRecord::SchemaMigration.normalize_migration_number(version) }
file_list = []
ActiveRecord::Migrator.migrations_paths.each do |path|
Dir.foreach(path) do |file|
# match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
if match_data = /^(\d{3,})_(.+)\.rb$/.match(file)
- status = db_list.delete(match_data[1]) ? 'up' : 'down'
- file_list << [status, match_data[1], match_data[2].humanize]
+ version = ActiveRecord::SchemaMigration.normalize_migration_number(match_data[1])
+ status = db_list.delete(version) ? 'up' : 'down'
+ file_list << [status, version, match_data[2].humanize]
end
end
end
@@ -278,7 +278,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
@@ -367,7 +367,7 @@ namespace :railties do
task :migrations => :'db:load_config' do
to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }
railties = {}
- Rails.application.railties.each do |railtie|
+ Rails.application.migration_railties.each do |railtie|
next unless to_load == :all || to_load.include?(railtie.railtie_name)
if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first)
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 4fde6677be..51c96373ee 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,12 +1,14 @@
+require 'thread'
+
module ActiveRecord
# = Active Record Reflection
module Reflection # :nodoc:
extend ActiveSupport::Concern
included do
- class_attribute :reflections
+ class_attribute :_reflections
class_attribute :aggregate_reflections
- self.reflections = {}
+ self._reflections = {}
self.aggregate_reflections = {}
end
@@ -22,7 +24,7 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
- ar.reflections = ar.reflections.merge(name.to_s => reflection)
+ ar._reflections = ar._reflections.merge(name.to_s => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
@@ -51,6 +53,24 @@ module ActiveRecord
aggregate_reflections[aggregation.to_s]
end
+ # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
+ #
+ # Account.reflections # => {balance: AggregateReflection}
+ #
+ # @api public
+ def reflections
+ ref = {}
+ _reflections.each do |name, reflection|
+ parent_name, parent_reflection = reflection.parent_reflection
+ if parent_name
+ ref[parent_name] = parent_reflection
+ else
+ ref[name] = reflection
+ end
+ end
+ ref
+ end
+
# Returns an array of AssociationReflection objects for all the
# associations in the class. If you only want to reflect on a certain
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
@@ -61,6 +81,7 @@ module ActiveRecord
# Account.reflect_on_all_associations # returns an array of all associations
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
#
+ # @api public
def reflect_on_all_associations(macro = nil)
association_reflections = reflections.values
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
@@ -71,11 +92,19 @@ module ActiveRecord
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
+ # @api public
def reflect_on_association(association)
reflections[association.to_s]
end
+ # @api private
+ def _reflect_on_association(association) #:nodoc:
+ _reflections[association.to_s]
+ end
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
+ #
+ # @api public
def reflect_on_all_autosave_associations
reflections.values.select { |reflection| reflection.options[:autosave] }
end
@@ -127,6 +156,10 @@ module ActiveRecord
def autosave=(autosave)
@automatic_inverse_of = false
@options[:autosave] = autosave
+ _, parent_reflection = self.parent_reflection
+ if parent_reflection
+ parent_reflection.autosave = autosave
+ end
end
# Returns the class for the macro.
@@ -155,6 +188,23 @@ module ActiveRecord
active_record == other_aggregation.active_record
end
+ JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
+
+ def join_keys(assoc_klass)
+ if source_macro == :belongs_to
+ if polymorphic?
+ reflection_key = association_primary_key(assoc_klass)
+ else
+ reflection_key = association_primary_key
+ end
+ reflection_foreign_key = foreign_key
+ else
+ reflection_key = foreign_key
+ reflection_foreign_key = active_record_primary_key
+ end
+ JoinKeys.new(reflection_key, reflection_foreign_key)
+ end
+
private
def derive_class_name
name.to_s.camelize
@@ -191,14 +241,27 @@ module ActiveRecord
end
attr_reader :type, :foreign_type
+ attr_accessor :parent_reflection # [:name, Reflection]
def initialize(macro, name, scope, options, active_record)
super
- @collection = :has_many == macro
+ @collection = macro == :has_many
@automatic_inverse_of = nil
@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 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
@@ -257,13 +320,32 @@ module ActiveRecord
end
def check_validity_of_inverse!
- unless options[:polymorphic]
+ unless polymorphic?
if has_inverse? && inverse_of.nil?
raise InverseOfAssociationNotFoundError.new(self)
end
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
@@ -297,12 +379,12 @@ module ActiveRecord
def inverse_of
return unless inverse_name
- @inverse_of ||= klass.reflect_on_association inverse_name
+ @inverse_of ||= klass._reflect_on_association inverse_name
end
def polymorphic_inverse_of(associated_class)
if has_inverse?
- if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
+ if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
inverse_relationship
else
raise InverseOfAssociationNotFoundError.new(self, associated_class)
@@ -327,7 +409,7 @@ module ActiveRecord
# * you use autosave; <tt>autosave: true</tt>
# * the association is a +has_many+ association
def validate?
- !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
end
# Returns +true+ if +self+ is a +belongs_to+ reflection.
@@ -335,10 +417,15 @@ module ActiveRecord
macro == :belongs_to
end
+ # Returns +true+ if +self+ is a +has_one+ reflection.
+ def has_one?
+ macro == :has_one
+ end
+
def association_class
case macro
when :belongs_to
- if options[:polymorphic]
+ if polymorphic?
Associations::BelongsToPolymorphicAssociation
else
Associations::BelongsToAssociation
@@ -359,7 +446,7 @@ module ActiveRecord
end
def polymorphic?
- options.key? :polymorphic
+ options[:polymorphic]
end
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
@@ -376,7 +463,7 @@ module ActiveRecord
def calculate_constructable(macro, options)
case macro
when :belongs_to
- !options[:polymorphic]
+ !polymorphic?
when :has_one
!options[:through]
else
@@ -400,10 +487,10 @@ module ActiveRecord
# returns either nil or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
- inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name).to_sym
begin
- reflection = klass.reflect_on_association(inverse_name)
+ reflection = klass._reflect_on_association(inverse_name)
rescue NameError
# Give up: we couldn't compute the klass type so we won't be able
# to find any associations either.
@@ -473,6 +560,13 @@ module ActiveRecord
end
end
+ class HasAndBelongsToManyReflection < AssociationReflection #:nodoc:
+ def initialize(macro, name, scope, options, active_record)
+ super
+ @collection = true
+ end
+ end
+
# Holds all the meta-data about a :through association as it was specified
# in the Active Record class.
class ThroughReflection < AssociationReflection #:nodoc:
@@ -502,7 +596,7 @@ module ActiveRecord
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
#
def source_reflection
- through_reflection.klass.reflect_on_association(source_reflection_name)
+ through_reflection.klass._reflect_on_association(source_reflection_name)
end
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
@@ -518,7 +612,7 @@ module ActiveRecord
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
#
def through_reflection
- active_record.reflect_on_association(options[:through])
+ active_record._reflect_on_association(options[:through])
end
# Returns an array of reflections which are involved in this association. Each item in the
@@ -625,7 +719,7 @@ module ActiveRecord
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
names = names.find_all { |n|
- through_reflection.klass.reflect_on_association(n)
+ through_reflection.klass._reflect_on_association(n)
}
if names.length > 1
@@ -658,7 +752,7 @@ directive on your declaration like:
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
end
- if through_reflection.options[:polymorphic]
+ if through_reflection.polymorphic?
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
end
@@ -666,15 +760,15 @@ directive on your declaration like:
raise HasManyThroughSourceAssociationNotFoundError.new(self)
end
- if options[:source_type] && source_reflection.options[:polymorphic].nil?
+ if options[:source_type] && !source_reflection.polymorphic?
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
end
- if source_reflection.options[:polymorphic] && options[:source_type].nil?
+ if source_reflection.polymorphic? && options[:source_type].nil?
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
end
- if macro == :has_one && through_reflection.collection?
+ if has_one? && through_reflection.collection?
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 709edbee88..cef40be7ce 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
@@ -72,7 +74,14 @@ module ActiveRecord
def _update_record(values, id, id_was) # :nodoc:
substitutes, binds = substitute_values values
- um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
+
+ scope = @klass.unscoped
+
+ if @klass.finder_needs_type_condition?
+ scope.unscope!(where: @klass.inheritance_column)
+ end
+
+ um = scope.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
@klass.connection.update(
um,
@@ -223,6 +232,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
@@ -323,7 +333,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.
@@ -427,12 +438,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
@@ -516,11 +536,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
@@ -537,7 +557,13 @@ module ActiveRecord
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
@@ -601,7 +627,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 514ebc2bfe..028e4d80ab 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.
@@ -168,10 +176,8 @@ module ActiveRecord
}
end
- result = result.map do |attributes|
- values = klass.initialize_attributes(attributes).values
-
- columns.zip(values).map { |column, value| column.type_cast value }
+ result = result.rows.map do |values|
+ columns.zip(values).map { |column, value| column.type_cast_from_database value }
end
columns.one? ? result.map!(&:first) : result
end
@@ -235,11 +241,14 @@ module ActiveRecord
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 +258,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
@@ -265,8 +275,8 @@ module ActiveRecord
group_attrs = group_values
if group_attrs.first.respond_to?(:to_sym)
- association = @klass.reflect_on_association(group_attrs.first.to_sym)
- associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
+ association = @klass._reflect_on_association(group_attrs.first.to_sym)
+ associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attrs)
else
group_fields = group_attrs
@@ -367,7 +377,7 @@ module ActiveRecord
end
def type_cast_using_column(value, column)
- column ? column.type_cast(value) : value
+ column ? column.type_cast_from_database(value) : value
end
# TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
@@ -385,9 +395,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 7af4b29ebc..0c9c761f97 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -65,7 +65,7 @@ module ActiveRecord
# # returns an Array of the required fields, available since Rails 3.1.
def find(*args)
if block_given?
- to_a.find { |*block_args| yield(*block_args) }
+ to_a.find(*args) { |*block_args| yield(*block_args) }
else
find_with_ids(*args)
end
@@ -242,7 +242,7 @@ 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(41, offset_index)
@@ -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
@@ -339,7 +336,16 @@ module ActiveRecord
end
def find_with_associations
- join_dependency = construct_join_dependency
+ # NOTE: the JoinDependency constructed here needs to know about
+ # any joins already present in `self`, so pass them in
+ #
+ # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
+ # incorrect SQL is generated. In that case, the join dependency for
+ # SpecialCategorizations is constructed without knowledge of the
+ # preexisting join in joins_values to categorizations (by way of
+ # the `has_many :through` for categories).
+ #
+ join_dependency = construct_join_dependency(joins_values)
aliases = join_dependency.aliases
relation = select aliases.columns
@@ -351,7 +357,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
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index be44fccad5..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
@@ -148,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..eff5c8f09c 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -26,7 +26,7 @@ module ActiveRecord
queries << '1=0'
else
table = Arel::Table.new(column, default_table.engine)
- association = klass.reflect_on_association(column.to_sym)
+ association = klass._reflect_on_association(column.to_sym)
value.each do |k, v|
queries.concat expand(association && association.klass, table, k, v)
@@ -55,7 +55,7 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if klass && reflection = klass.reflect_on_association(column.to_sym)
+ if klass && reflection = klass._reflect_on_association(column.to_sym)
if reflection.polymorphic? && base_class = polymorphic_base_class_from_value(value)
queries << build(table[reflection.foreign_type], base_class)
end
@@ -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/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index 2f6c34ac08..78dba8be06 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -2,28 +2,33 @@ module ActiveRecord
class PredicateBuilder
class ArrayHandler # :nodoc:
def call(attribute, value)
+ return attribute.in([]) if value.empty?
+
values = value.map { |x| x.is_a?(Base) ? x.id : x }
ranges, values = values.partition { |v| v.is_a?(Range) }
+ nils, values = values.partition(&:nil?)
- values_predicate = if values.include?(nil)
- values = values.compact
-
+ values_predicate =
case values.length
- when 0
- attribute.eq(nil)
- when 1
- attribute.eq(values.first).or(attribute.eq(nil))
- else
- attribute.in(values).or(attribute.eq(nil))
+ when 0 then NullPredicate
+ when 1 then attribute.eq(values.first)
+ else attribute.in(values)
end
- else
- attribute.in(values)
+
+ unless nils.empty?
+ values_predicate = values_predicate.or(attribute.eq(nil))
end
array_predicates = ranges.map { |range| attribute.in(range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
end
+
+ module NullPredicate
+ def self.or(other)
+ other
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4287304945..1262b2c291 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
@@ -561,15 +573,11 @@ module ActiveRecord
end
end
- def where!(opts = :chain, *rest) # :nodoc:
- if opts == :chain
- WhereChain.new(self)
- else
- references!(PredicateBuilder.references(opts)) if Hash === opts
+ def where!(opts, *rest) # :nodoc:
+ references!(PredicateBuilder.references(opts)) if Hash === opts
- self.where_values += build_where(opts, rest)
- self
- end
+ self.where_values += build_where(opts, rest)
+ self
end
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
@@ -843,7 +851,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?
@@ -860,6 +868,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
@@ -874,6 +891,8 @@ module ActiveRecord
case scope
when :order
result = []
+ when :where
+ self.bind_values = []
else
result = [] unless single_val_method
end
@@ -924,18 +943,15 @@ 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
@@ -946,6 +962,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
@@ -987,9 +1026,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)
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/result.rb b/activerecord/lib/active_record/result.rb
index 228b2aa60f..8c29c69a9b 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -31,7 +31,7 @@ module ActiveRecord
class Result
include Enumerable
- IDENTITY_TYPE = Class.new { def type_cast(v); v; end }.new # :nodoc:
+ IDENTITY_TYPE = Type::Value.new # :nodoc:
attr_reader :columns, :rows, :column_types
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index be62e41932..ff70cbed0f 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,7 +107,7 @@ module ActiveRecord
end.join(', ')
end
- # Sanitizes a +string+ so that it is safe to use within a sql
+ # Sanitizes a +string+ so that it is safe to use within an 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, "%", "_")
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index a9d164e366..3a004d58c9 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -5,6 +5,9 @@ require 'active_record/base'
module ActiveRecord
class SchemaMigration < ActiveRecord::Base
class << self
+ def primary_key
+ nil
+ end
def table_name
"#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
@@ -36,6 +39,10 @@ module ActiveRecord
connection.drop_table(table_name)
end
end
+
+ def normalize_migration_number(number)
+ "%.3d" % number.to_i
+ end
end
def version
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 1a766093d0..c2484d02ed 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -180,13 +180,9 @@ module ActiveRecord #:nodoc:
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
- type = if klass.serialized_attributes.key?(name)
- super
- elsif klass.columns_hash.key?(name)
- klass.columns_hash[name].type
- else
- NilClass
- end
+ column = klass.columns_hash[name] || Type::Value.new
+
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type
{ :text => :string,
:time => :datetime }[type] || type
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 168b338b97..649f316ed8 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -6,7 +6,7 @@ module ActiveRecord
# <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates
# logic behind common tasks used to manage database and migrations.
#
- # The tasks defined here are used in rake tasks provided by Active Record.
+ # The tasks defined here are used with Rake tasks provided by Active Record.
#
# In order to use DatabaseTasks, a few config values need to be set. All the needed
# config values are set by Rails already, so it's necessary to do it only if you
@@ -14,7 +14,6 @@ module ActiveRecord
# (in such case after configuring the database tasks, you can also use the rake tasks
# defined in Active Record).
#
- #
# The possible config values are:
#
# * +env+: current environment (like Rails.env).
@@ -161,10 +160,12 @@ module ActiveRecord
when :ruby
file ||= File.join(db_dir, "schema.rb")
check_schema_file(file)
+ purge(current_config)
load(file)
when :sql
file ||= File.join(db_dir, "structure.sql")
check_schema_file(file)
+ purge(current_config)
structure_load(current_config, file)
else
raise ArgumentError, "unknown format #{format.inspect}"
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index c755831e6d..644c4852b9 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -124,7 +124,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
end
def root_password
- $stdout.print "Please provide the root password for your mysql installation\n>"
+ $stdout.print "Please provide the root password for your MySQL installation\n>"
$stdin.gets.strip
end
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index 5688931db2..9ab64d0325 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -21,7 +21,11 @@ module ActiveRecord
FileUtils.rm(file) if File.exist?(file)
end
- alias :purge :drop
+
+ def purge
+ drop
+ create
+ end
def charset
connection.encoding
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 6c30ccab72..e2e37e7c00 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -99,9 +99,11 @@ module ActiveRecord
end
def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update)
- if (timestamps = timestamp_names.map { |attr| self[attr] }.compact).present?
- timestamps.map { |ts| ts.to_time }.max
- end
+ timestamp_names
+ .map { |attr| self[attr] }
+ .compact
+ .map(&:to_time)
+ .max
end
def current_time_from_proper_timezone
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 17f76b63b3..7e4dc4c895 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
@@ -341,7 +339,7 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
- @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
+ @_start_transaction_state[:id] = id
unless @_start_transaction_state.include?(:new_record)
@_start_transaction_state[:new_record] = @new_record
end
@@ -349,13 +347,18 @@ module ActiveRecord
@_start_transaction_state[:destroyed] = @destroyed
end
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- @_start_transaction_state[:frozen?] = @attributes.frozen?
+ @_start_transaction_state[:frozen?] = frozen?
end
# 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.
@@ -364,17 +367,10 @@ module ActiveRecord
transaction_level = (@_start_transaction_state[:level] || 0) - 1
if transaction_level < 1 || force
restore_state = @_start_transaction_state
- was_frozen = restore_state[:frozen?]
- @attributes = @attributes.dup if @attributes.frozen?
+ thaw unless restore_state[:frozen?]
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
- if restore_state.has_key?(:id)
- write_attribute(self.class.primary_key, restore_state[:id])
- else
- @attributes.delete(self.class.primary_key)
- @attributes_cache.delete(self.class.primary_key)
- end
- @attributes.freeze if was_frozen
+ write_attribute(self.class.primary_key, restore_state[:id])
end
end
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
new file mode 100644
index 0000000000..f1384e0bb2
--- /dev/null
+++ b/activerecord/lib/active_record/type.rb
@@ -0,0 +1,20 @@
+require 'active_record/type/mutable'
+require 'active_record/type/numeric'
+require 'active_record/type/time_value'
+require 'active_record/type/value'
+
+require 'active_record/type/binary'
+require 'active_record/type/boolean'
+require 'active_record/type/date'
+require 'active_record/type/date_time'
+require 'active_record/type/decimal'
+require 'active_record/type/decimal_without_scale'
+require 'active_record/type/float'
+require 'active_record/type/integer'
+require 'active_record/type/serialized'
+require 'active_record/type/string'
+require 'active_record/type/text'
+require 'active_record/type/time'
+
+require 'active_record/type/type_map'
+require 'active_record/type/hash_lookup_type_map'
diff --git a/activerecord/lib/active_record/type/binary.rb b/activerecord/lib/active_record/type/binary.rb
new file mode 100644
index 0000000000..3bf29b5026
--- /dev/null
+++ b/activerecord/lib/active_record/type/binary.rb
@@ -0,0 +1,40 @@
+module ActiveRecord
+ module Type
+ class Binary < Value # :nodoc:
+ def type
+ :binary
+ end
+
+ def binary?
+ true
+ end
+
+ def type_cast(value)
+ if value.is_a?(Data)
+ value.to_s
+ else
+ super
+ end
+ end
+
+ def type_cast_for_database(value)
+ return if value.nil?
+ Data.new(super)
+ end
+
+ class Data
+ def initialize(value)
+ @value = value
+ end
+
+ def to_s
+ @value
+ end
+
+ def hex
+ @value.unpack('H*')[0]
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb
new file mode 100644
index 0000000000..06dd17ed28
--- /dev/null
+++ b/activerecord/lib/active_record/type/boolean.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module Type
+ class Boolean < Value # :nodoc:
+ def type
+ :boolean
+ end
+
+ private
+
+ def cast_value(value)
+ if value == ''
+ nil
+ else
+ ConnectionAdapters::Column::TRUE_VALUES.include?(value)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/date.rb b/activerecord/lib/active_record/type/date.rb
new file mode 100644
index 0000000000..d90a6069b7
--- /dev/null
+++ b/activerecord/lib/active_record/type/date.rb
@@ -0,0 +1,46 @@
+module ActiveRecord
+ module Type
+ class Date < Value # :nodoc:
+ def type
+ :date
+ end
+
+ def klass
+ ::Date
+ end
+
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ 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 =~ ConnectionAdapters::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
diff --git a/activerecord/lib/active_record/type/date_time.rb b/activerecord/lib/active_record/type/date_time.rb
new file mode 100644
index 0000000000..560d63c101
--- /dev/null
+++ b/activerecord/lib/active_record/type/date_time.rb
@@ -0,0 +1,33 @@
+module ActiveRecord
+ 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
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
new file mode 100644
index 0000000000..a9db51c6ba
--- /dev/null
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -0,0 +1,25 @@
+module ActiveRecord
+ module Type
+ class Decimal < Value # :nodoc:
+ include Numeric
+
+ def type
+ :decimal
+ end
+
+ def type_cast_for_schema(value)
+ value.to_s
+ 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
diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb
new file mode 100644
index 0000000000..cabdcecdd7
--- /dev/null
+++ b/activerecord/lib/active_record/type/decimal_without_scale.rb
@@ -0,0 +1,11 @@
+require 'active_record/type/integer'
+
+module ActiveRecord
+ module Type
+ class DecimalWithoutScale < Integer # :nodoc:
+ def type
+ :decimal
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/float.rb b/activerecord/lib/active_record/type/float.rb
new file mode 100644
index 0000000000..42eb44b9a9
--- /dev/null
+++ b/activerecord/lib/active_record/type/float.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module Type
+ class Float < Value # :nodoc:
+ include Numeric
+
+ def type
+ :float
+ end
+
+ alias type_cast_for_database type_cast
+
+ private
+
+ def cast_value(value)
+ value.to_f
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
new file mode 100644
index 0000000000..bf92680268
--- /dev/null
+++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module Type
+ class HashLookupTypeMap < TypeMap # :nodoc:
+ delegate :key?, to: :@mapping
+
+ def lookup(type, *args)
+ @mapping.fetch(type, proc { default_value }).call(type, *args)
+ end
+
+ def fetch(type, *args, &block)
+ @mapping.fetch(type, block).call(type, *args)
+ end
+
+ def alias_type(type, alias_type)
+ register_type(type) { |_, *args| lookup(alias_type, *args) }
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb
new file mode 100644
index 0000000000..08477d1303
--- /dev/null
+++ b/activerecord/lib/active_record/type/integer.rb
@@ -0,0 +1,23 @@
+module ActiveRecord
+ module Type
+ class Integer < Value # :nodoc:
+ include Numeric
+
+ def type
+ :integer
+ end
+
+ alias type_cast_for_database type_cast
+
+ 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
diff --git a/activerecord/lib/active_record/type/mutable.rb b/activerecord/lib/active_record/type/mutable.rb
new file mode 100644
index 0000000000..64cf4b9b93
--- /dev/null
+++ b/activerecord/lib/active_record/type/mutable.rb
@@ -0,0 +1,16 @@
+module ActiveRecord
+ module Type
+ module Mutable
+ def type_cast_from_user(value)
+ type_cast_from_database(type_cast_for_database(value))
+ end
+
+ # +raw_old_value+ will be the `_before_type_cast` version of the
+ # value (likely a string). +new_value+ will be the current, type
+ # cast value.
+ def changed_in_place?(raw_old_value, new_value) # :nodoc:
+ raw_old_value != type_cast_for_database(new_value)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
new file mode 100644
index 0000000000..137c9e4c99
--- /dev/null
+++ b/activerecord/lib/active_record/type/numeric.rb
@@ -0,0 +1,42 @@
+module ActiveRecord
+ module Type
+ module Numeric # :nodoc:
+ def number?
+ true
+ end
+
+ def type_cast(value)
+ value = case value
+ when true then 1
+ when false then 0
+ when ::String then value.presence
+ else value
+ end
+ super(value)
+ end
+
+ def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
+ # 0 => 'wibble' should mark as changed so numericality validations run
+ if nil_or_zero?(old_value) && non_numeric_string?(new_value_before_type_cast)
+ # nil => '' should not mark as changed
+ old_value != new_value_before_type_cast.presence
+ else
+ super
+ end
+ end
+
+ private
+
+ def non_numeric_string?(value)
+ # 'wibble'.to_i will give zero, we want to make sure
+ # that we aren't marking int zero to string zero as
+ # changed.
+ value !~ /\A\d+\.?\d*\z/
+ end
+
+ def nil_or_zero?(value)
+ value.nil? || value == 0
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
new file mode 100644
index 0000000000..42bbed7103
--- /dev/null
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -0,0 +1,51 @@
+module ActiveRecord
+ module Type
+ class Serialized < SimpleDelegator # :nodoc:
+ include Mutable
+
+ attr_reader :subtype, :coder
+
+ def initialize(subtype, coder)
+ @subtype = subtype
+ @coder = coder
+ super(subtype)
+ end
+
+ def type_cast_from_database(value)
+ if is_default_value?(value)
+ value
+ else
+ coder.load(super)
+ end
+ end
+
+ def type_cast_for_database(value)
+ return if value.nil?
+ unless is_default_value?(value)
+ super coder.dump(value)
+ end
+ end
+
+ def accessor
+ ActiveRecord::Store::IndifferentHashAccessor
+ end
+
+ def init_with(coder)
+ @subtype = coder['subtype']
+ @coder = coder['coder']
+ __setobj__(@subtype)
+ end
+
+ def encode_with(coder)
+ coder['subtype'] = @subtype
+ coder['coder'] = @coder
+ end
+
+ private
+
+ def is_default_value?(value)
+ value == coder.load(nil)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb
new file mode 100644
index 0000000000..3b1554bd5a
--- /dev/null
+++ b/activerecord/lib/active_record/type/string.rb
@@ -0,0 +1,23 @@
+module ActiveRecord
+ module Type
+ class String < Value # :nodoc:
+ def type
+ :string
+ end
+
+ def text?
+ true
+ 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
diff --git a/activerecord/lib/active_record/type/text.rb b/activerecord/lib/active_record/type/text.rb
new file mode 100644
index 0000000000..26f980f060
--- /dev/null
+++ b/activerecord/lib/active_record/type/text.rb
@@ -0,0 +1,11 @@
+require 'active_record/type/string'
+
+module ActiveRecord
+ module Type
+ class Text < String # :nodoc:
+ def type
+ :text
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
new file mode 100644
index 0000000000..41f7d97f0c
--- /dev/null
+++ b/activerecord/lib/active_record/type/time.rb
@@ -0,0 +1,26 @@
+module ActiveRecord
+ 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
diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb
new file mode 100644
index 0000000000..d611d72dd4
--- /dev/null
+++ b/activerecord/lib/active_record/type/time_value.rb
@@ -0,0 +1,38 @@
+module ActiveRecord
+ module Type
+ module TimeValue # :nodoc:
+ def klass
+ ::Time
+ end
+
+ def type_cast_for_schema(value)
+ "'#{value.to_s(:db)}'"
+ 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 =~ ConnectionAdapters::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
diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb
new file mode 100644
index 0000000000..88c5f9c497
--- /dev/null
+++ b/activerecord/lib/active_record/type/type_map.rb
@@ -0,0 +1,48 @@
+module ActiveRecord
+ module Type
+ class TypeMap # :nodoc:
+ def initialize
+ @mapping = {}
+ end
+
+ def lookup(lookup_key, *args)
+ matching_pair = @mapping.reverse_each.detect do |key, _|
+ key === lookup_key
+ end
+
+ if matching_pair
+ matching_pair.last.call(lookup_key, *args)
+ 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, *args|
+ metadata = sql_type[/\(.*\)/, 0]
+ lookup("#{target_key}#{metadata}", *args)
+ end
+ end
+
+ def clear
+ @mapping.clear
+ end
+
+ private
+
+ def default_value
+ @default_value ||= Value.new
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
new file mode 100644
index 0000000000..875fb98c4b
--- /dev/null
+++ b/activerecord/lib/active_record/type/value.rb
@@ -0,0 +1,78 @@
+module ActiveRecord
+ module Type
+ class Value # :nodoc:
+ attr_reader :precision, :scale, :limit
+
+ # Valid options are +precision+, +scale+, and +limit+.
+ # They are only used when dumping schema.
+ def initialize(options = {})
+ options.assert_valid_keys(:precision, :scale, :limit)
+ @precision = options[:precision]
+ @scale = options[:scale]
+ @limit = options[:limit]
+ end
+
+ # The simplified type that this object represents. Subclasses
+ # must override this method.
+ def type; end
+
+ def type_cast_from_database(value)
+ type_cast(value)
+ end
+
+ def type_cast_from_user(value)
+ type_cast(value)
+ end
+
+ def type_cast_for_database(value)
+ value
+ end
+
+ def type_cast_for_schema(value)
+ value.inspect
+ end
+
+ def text?
+ false
+ end
+
+ def number?
+ false
+ end
+
+ def binary?
+ false
+ end
+
+ def klass # :nodoc:
+ end
+
+ # +old_value+ will always be type-cast.
+ # +new_value+ will come straight from the database
+ # or from assignment, so it could be anything. Types
+ # which cannot typecast arbitrary values should override
+ # this method.
+ def changed?(old_value, new_value, _new_value_before_type_cast) # :nodoc:
+ old_value != new_value
+ end
+
+ def changed_in_place?(*) # :nodoc:
+ false
+ end
+
+ private
+ # Takes an input from the database, or from attribute setters,
+ # and casts it to a type appropriate for this object. This method
+ # should not be overriden by subclasses. Instead, override `cast_value`.
+ def type_cast(value) # :api: public
+ cast_value(value) unless value.nil?
+ end
+
+ # Responsible for casting values from external sources to the appropriate
+ # type. Called by `type_cast` for all values except `nil`.
+ def cast_value(value) # :api: public
+ value
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 9a19483da3..e586744818 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -4,7 +4,7 @@ module ActiveRecord
def validate(record)
super
attributes.each do |attribute|
- next unless record.class.reflect_on_association(attribute)
+ next unless record.class._reflect_on_association(attribute)
associated_records = Array.wrap(record.send(attribute))
# Superclass validates presence. Ensure present records aren't about to be destroyed.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index ee080451a9..2e7b1d7206 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -14,7 +14,6 @@ module ActiveRecord
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
- value = deserialize_attribute(record, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
@@ -47,7 +46,7 @@ module ActiveRecord
end
def build_relation(klass, table, attribute, value) #:nodoc:
- if reflection = klass.reflect_on_association(attribute)
+ if reflection = klass._reflect_on_association(attribute)
attribute = reflection.foreign_key
value = value.attributes[reflection.primary_key_column.name] unless value.nil?
end
@@ -74,7 +73,7 @@ module ActiveRecord
def scope_relation(record, table, relation)
Array(options[:scope]).each do |scope_item|
- if reflection = record.class.reflect_on_association(scope_item)
+ if reflection = record.class._reflect_on_association(scope_item)
scope_value = record.send(reflection.foreign_key)
scope_item = reflection.foreign_key
else
@@ -86,12 +85,6 @@ module ActiveRecord
relation
end
- def deserialize_attribute(record, attribute, value)
- coder = record.class.serialized_attributes[attribute.to_s]
- value = coder.dump value if value && coder
- value
- end
-
def map_enum_attribute(klass, attribute, value)
mapping = klass.defined_enums[attribute.to_s]
value = mapping[value] if value && mapping
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/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb
new file mode 100644
index 0000000000..083d533bb2
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql/consistency_test.rb
@@ -0,0 +1,48 @@
+require "cases/helper"
+
+class MysqlConsistencyTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class Consistency < ActiveRecord::Base
+ self.table_name = "mysql_consistency"
+ end
+
+ setup do
+ @old_emulate_booleans = ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
+
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table("mysql_consistency") do |t|
+ t.boolean "a_bool"
+ t.string "a_string"
+ end
+ Consistency.reset_column_information
+ Consistency.create!
+ end
+
+ teardown do
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = @old_emulate_booleans
+ @connection.drop_table "mysql_consistency"
+ end
+
+ test "boolean columns with random value type cast to 0 when emulate_booleans is false" do
+ with_new = Consistency.new
+ with_last = Consistency.last
+ with_new.a_bool = 'wibble'
+ with_last.a_bool = 'wibble'
+
+ assert_equal 0, with_new.a_bool
+ assert_equal 0, with_last.a_bool
+ end
+
+ test "string columns call #to_s" do
+ with_new = Consistency.new
+ with_last = Consistency.last
+ thing = Object.new
+ with_new.a_string = thing
+ with_last.a_string = thing
+
+ assert_equal thing.to_s, with_new.a_string
+ assert_equal thing.to_s, with_last.a_string
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 1699380eb3..28106d3772 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -105,7 +105,7 @@ module ActiveRecord
result = @conn.exec_query('SELECT status FROM ex')
- assert_equal 2, result.column_types['status'].type_cast(result.last['status'])
+ assert_equal 2, result.column_types['status'].type_cast_from_database(result.last['status'])
end
end
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..0b1e3295cc 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -1,9 +1,9 @@
# encoding: utf-8
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlArrayTest < ActiveRecord::TestCase
+ include InTimeZone
+
class PgArray < ActiveRecord::Base
self.table_name = 'pg_arrays'
end
@@ -14,9 +14,10 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
@connection.create_table('pg_arrays') do |t|
t.string 'tags', array: true
t.integer 'ratings', array: true
+ t.datetime :datetimes, array: true
end
end
- @column = PgArray.columns.find { |c| c.name == 'tags' }
+ @column = PgArray.columns_hash['tags']
end
teardown do
@@ -61,10 +62,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,38 +81,57 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
end
end
- def test_type_cast_array
- data = '{1,2,3}'
- oid_type = @column.instance_variable_get('@oid_type').subtype
- # we are getting the instance variable in this test, but in the
- # normal use of string_to_array, it's called from the OID::Array
- # class and will have the OID instance that will provide the type
- # casting
- array = @column.class.string_to_array data, oid_type
- assert_equal(['1', '2', '3'], array)
- assert_equal(['1', '2', '3'], @column.type_cast(data))
+ def test_change_column_default_with_array
+ @connection.change_column_default :pg_arrays, :tags, []
- assert_equal([], @column.type_cast('{}'))
- assert_equal([nil], @column.type_cast('{NULL}'))
+ PgArray.reset_column_information
+ column = PgArray.columns_hash['tags']
+ assert_equal [], column.default
+ end
+
+ def test_type_cast_array
+ assert_equal(['1', '2', '3'], @column.type_cast_from_database('{1,2,3}'))
+ assert_equal([], @column.type_cast_from_database('{}'))
+ assert_equal([nil], @column.type_cast_from_database('{NULL}'))
end
def test_type_cast_integers
x = PgArray.new(ratings: ['1', '2'])
- assert x.save!
- assert_equal(['1', '2'], x.ratings)
+
+ assert_equal([1, 2], x.ratings)
+
+ x.save!
+ x.reload
+
+ 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 +195,34 @@ 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
+
+ def test_datetime_with_timezone_awareness
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PgArray.reset_column_information
+ time_string = Time.current.to_s
+ time = Time.zone.parse(time_string)
+
+ record = PgArray.new(datetimes: [time_string])
+ assert_equal [time], record.datetimes
+ assert_equal ActiveSupport::TimeZone[tz], record.datetimes.first.time_zone
+
+ record.save!
+ record.reload
+
+ assert_equal [time], record.datetimes
+ assert_equal ActiveSupport::TimeZone[tz], record.datetimes.first.time_zone
+ end
+ end
+
private
def assert_cycle field, array
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
new file mode 100644
index 0000000000..3a9397bc26
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'support/schema_dumping_helper'
+
+class PostgresqlBitStringTest < ActiveRecord::TestCase
+ include ConnectionHelper
+ include SchemaDumpingHelper
+
+ class PostgresqlBitString < ActiveRecord::Base; end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table('postgresql_bit_strings', :force => true) do |t|
+ t.bit :a_bit, default: "00000011", limit: 8
+ t.bit_varying :a_bit_varying, default: "0011", limit: 4
+ end
+ end
+
+ def teardown
+ return unless @connection
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_bit_strings'
+ end
+
+ def test_bit_string_column
+ column = PostgresqlBitString.columns_hash["a_bit"]
+ assert_equal :bit, column.type
+ assert_equal "bit(8)", column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_bit_string_varying_column
+ column = PostgresqlBitString.columns_hash["a_bit_varying"]
+ assert_equal :bit_varying, column.type
+ assert_equal "bit varying(4)", column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_default
+ column = PostgresqlBitString.columns_hash["a_bit"]
+ assert_equal "00000011", column.default
+ assert_equal "00000011", PostgresqlBitString.new.a_bit
+
+ column = PostgresqlBitString.columns_hash["a_bit_varying"]
+ assert_equal "0011", column.default
+ assert_equal "0011", PostgresqlBitString.new.a_bit_varying
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("postgresql_bit_strings")
+ assert_match %r{t\.bit\s+"a_bit",\s+limit: 8,\s+default: "00000011"$}, output
+ assert_match %r{t\.bit_varying\s+"a_bit_varying",\s+limit: 4,\s+default: "0011"$}, output
+ end
+
+ def test_assigning_invalid_hex_string_raises_exception
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "FF" }
+ end
+
+ def test_roundtrip
+ PostgresqlBitString.create! a_bit: "00001010", a_bit_varying: "0101"
+ record = PostgresqlBitString.first
+ assert_equal "00001010", record.a_bit
+ assert_equal "0101", record.a_bit_varying
+
+ record.a_bit = "11111111"
+ record.a_bit_varying = "0xF"
+ record.save!
+
+ assert record.reload
+ assert_equal "11111111", record.a_bit
+ assert_equal "1111", record.a_bit_varying
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index e3478856c8..7872f91943 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlByteaTest < ActiveRecord::TestCase
class ByteaDataType < ActiveRecord::Base
@@ -19,7 +16,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
@@ -36,16 +33,16 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
data = "\u001F\x8B"
assert_equal('UTF-8', data.encoding.name)
- assert_equal('ASCII-8BIT', @column.type_cast(data).encoding.name)
+ assert_equal('ASCII-8BIT', @column.type_cast_from_database(data).encoding.name)
end
def test_type_cast_binary_value
data = "\u001F\x8B".force_encoding("BINARY")
- assert_equal(data, @column.type_cast(data))
+ assert_equal(data, @column.type_cast_from_database(data))
end
def test_type_case_nil
- assert_equal(nil, @column.type_cast(nil))
+ assert_equal(nil, @column.type_cast_from_database(nil))
end
def test_read_value
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index 948bf49a54..90e837d426 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require 'cases/helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
if ActiveRecord::Base.connection.supports_extensions?
class PostgresqlCitextTest < ActiveRecord::TestCase
@@ -50,7 +47,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..42c68cdae7 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
+require 'support/connection_helper'
+
+module PostgresqlCompositeBehavior
+ include ConnectionHelper
-class PostgresqlCompositeTest < ActiveRecord::TestCase
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 +27,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 +57,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 +68,66 @@ 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::Type::Value
+ def type; :full_address end
+
+ def type_cast_from_database(value)
+ if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/
+ FullAddress.new($1, $2)
+ end
+ end
+
+ def type_cast_from_user(value)
+ value
+ end
+
+ def type_cast_for_database(value)
+ return if value.nil?
+ "(#{value.city},#{value.street})"
+ end
+ end
+
+ FullAddress = Struct.new(:city, :street)
+
+ def setup
+ super
+
+ @connection.type_map.register_type "full_address", FullAddressType.new
+ 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")
+ 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/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 5f84c893c0..d26cda46fa 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -121,7 +121,7 @@ module ActiveRecord
name = @subscriber.payloads.last[:statement_name]
assert name
res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})")
- plan = res.column_types['QUERY PLAN'].type_cast res.rows.first.first
+ plan = res.column_types['QUERY PLAN'].type_cast_from_database res.rows.first.first
assert_operator plan.length, :>, 0
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index e7dda1a1af..a0a34e4b87 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -1,13 +1,6 @@
require "cases/helper"
+require 'support/ddl_helper'
-class PostgresqlArray < ActiveRecord::Base
-end
-
-class PostgresqlTsvector < ActiveRecord::Base
-end
-
-class PostgresqlMoney < ActiveRecord::Base
-end
class PostgresqlNumber < ActiveRecord::Base
end
@@ -15,18 +8,9 @@ end
class PostgresqlTime < ActiveRecord::Base
end
-class PostgresqlNetworkAddress < ActiveRecord::Base
-end
-
-class PostgresqlBitString < ActiveRecord::Base
-end
-
class PostgresqlOid < ActiveRecord::Base
end
-class PostgresqlTimestampWithZone < ActiveRecord::Base
-end
-
class PostgresqlLtree < ActiveRecord::Base
end
@@ -35,62 +19,23 @@ 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)
- @connection.execute("INSERT INTO postgresql_network_addresses (id, cidr_address, inet_address, mac_address) VALUES(1, '192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')")
- @first_network_address = PostgresqlNetworkAddress.find(1)
-
- @connection.execute("INSERT INTO postgresql_bit_strings (id, bit_string, bit_string_varying) VALUES (1, B'00010101', X'15')")
- @first_bit_string = PostgresqlBitString.find(1)
-
@connection.execute("INSERT INTO postgresql_oids (id, obj_id) VALUES (1, 1234)")
@first_oid = PostgresqlOid.find(1)
-
- @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')")
end
teardown do
- [PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, 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
+ [PostgresqlNumber, PostgresqlTime, PostgresqlOid].each(&:delete_all)
end
def test_data_type_of_number_types
@@ -103,57 +48,16 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type
end
- def test_data_type_of_network_address_types
- assert_equal :cidr, @first_network_address.column_for_attribute(:cidr_address).type
- assert_equal :inet, @first_network_address.column_for_attribute(:inet_address).type
- assert_equal :macaddr, @first_network_address.column_for_attribute(:mac_address).type
- end
-
- def test_data_type_of_bit_string_types
- assert_equal :string, @first_bit_string.column_for_attribute(:bit_string).type
- assert_equal :string, @first_bit_string.column_for_attribute(:bit_string_varying).type
- end
-
def test_data_type_of_oid_types
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
- assert @first_tsvector.save
- assert @first_tsvector.reload
- @first_tsvector.text_vector = new_text_vector
- assert @first_tsvector.save
- assert @first_tsvector.reload
- assert_equal new_text_vector, @first_tsvector.text_vector
- end
-
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
@@ -161,56 +65,10 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '-21 days', @first_time.scaled_time_interval
end
- def test_network_address_values_ipaddr
- cidr_address = IPAddr.new '192.168.0.0/24'
- inet_address = IPAddr.new '172.16.1.254'
-
- assert_equal cidr_address, @first_network_address.cidr_address
- assert_equal inet_address, @first_network_address.inet_address
- assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
- end
-
- def test_bit_string_values
- assert_equal '00010101', @first_bit_string.bit_string
- assert_equal '00010101', @first_bit_string.bit_string_varying
- end
-
def test_oid_values
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
@@ -229,51 +87,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '2 years 00:03:00', @first_time.time_interval
end
- def test_update_network_address
- new_inet_address = '10.1.2.3/32'
- new_cidr_address = '10.0.0.0/8'
- new_mac_address = 'bc:de:f0:12:34:56'
- @first_network_address.cidr_address = new_cidr_address
- @first_network_address.inet_address = new_inet_address
- @first_network_address.mac_address = new_mac_address
- assert @first_network_address.save
- assert @first_network_address.reload
- assert_equal @first_network_address.cidr_address, new_cidr_address
- assert_equal @first_network_address.inet_address, new_inet_address
- assert_equal @first_network_address.mac_address, new_mac_address
- end
-
- def test_update_bit_string
- new_bit_string = '11111111'
- new_bit_string_varying = '0xFF'
- @first_bit_string.bit_string = new_bit_string
- @first_bit_string.bit_string_varying = new_bit_string_varying
- assert @first_bit_string.save
- assert @first_bit_string.reload
- assert_equal new_bit_string, @first_bit_string.bit_string
- assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
- end
-
- def test_invalid_hex_string
- new_bit_string = 'FF'
- @first_bit_string.bit_string = new_bit_string
- assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save }
- end
-
- def test_invalid_network_address
- @first_network_address.cidr_address = 'invalid addr'
- assert_nil @first_network_address.cidr_address
- assert_equal 'invalid addr', @first_network_address.cidr_address_before_type_cast
- assert @first_network_address.save
-
- @first_network_address.reload
-
- @first_network_address.inet_address = 'invalid addr'
- assert_nil @first_network_address.inet_address
- assert_equal 'invalid addr', @first_network_address.inet_address_before_type_cast
- assert @first_network_address.save
- end
-
def test_update_oid
new_value = 567890
@first_oid.obj_id = new_value
@@ -281,30 +94,26 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert @first_oid.reload
assert_equal new_value, @first_oid.obj_id
end
+end
- def test_timestamp_with_zone_values_with_rails_time_zone_support
- with_timezone_config default: :utc, aware_attributes: true do
- @connection.reconnect!
+class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase
+ include DdlHelper
- @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
- assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
- assert_instance_of Time, @first_timestamp_with_zone.time
- end
- ensure
- @connection.reconnect!
+ setup do
+ @connection = ActiveRecord::Base.connection
end
- def test_timestamp_with_zone_values_without_rails_time_zone_support
- with_timezone_config default: :local, aware_attributes: false do
- @connection.reconnect!
- # make sure to use a non-UTC time zone
- @connection.execute("SET time zone 'America/Jamaica'", 'SCHEMA')
+ 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
- @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
- assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
- assert_instance_of Time, @first_timestamp_with_zone.time
+ 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
- ensure
- @connection.reconnect!
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 5286a847a4..fd7fdecff1 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
require "cases/helper"
require 'support/connection_helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlDomainTest < ActiveRecord::TestCase
include ConnectionHelper
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 4146b117f6..b809f1a79c 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -39,6 +39,17 @@ class PostgresqlEnumTest < ActiveRecord::TestCase
assert_not column.array
end
+ def test_enum_defaults
+ @connection.add_column 'postgresql_enums', 'good_mood', :mood, default: 'happy'
+ PostgresqlEnum.reset_column_information
+ column = PostgresqlEnum.columns_hash["good_mood"]
+
+ assert_equal "happy", column.default
+ assert_equal "happy", PostgresqlEnum.new.good_mood
+ ensure
+ PostgresqlEnum.reset_column_information
+ end
+
def test_enum_mapping
@connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
enum = PostgresqlEnum.first
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..7b99fcdda0
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -0,0 +1,63 @@
+require "cases/helper"
+
+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/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
new file mode 100644
index 0000000000..ec646de5e9
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+require "cases/helper"
+
+class PostgresqlFullTextTest < ActiveRecord::TestCase
+ class PostgresqlTsvector < ActiveRecord::Base; end
+
+ def test_tsvector_column
+ column = PostgresqlTsvector.columns_hash["text_vector"]
+ assert_equal :tsvector, column.type
+ assert_equal "tsvector", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_update_tsvector
+ PostgresqlTsvector.create text_vector: "'text' 'vector'"
+ tsvector = PostgresqlTsvector.first
+ assert_equal "'text' 'vector'", tsvector.text_vector
+
+ tsvector.text_vector = "'new' 'text' 'vector'"
+ tsvector.save!
+ assert tsvector.reload
+ assert_equal "'new' 'text' 'vector'", tsvector.text_vector
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
new file mode 100644
index 0000000000..2f106ee664
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'support/schema_dumping_helper'
+
+class PostgresqlPointTest < ActiveRecord::TestCase
+ include ConnectionHelper
+ include SchemaDumpingHelper
+
+ class PostgresqlPoint < ActiveRecord::Base; end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.create_table('postgresql_points') do |t|
+ t.point :x
+ t.point :y, default: [12.2, 13.3]
+ t.point :z, default: "(14.4,15.5)"
+ end
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_points'
+ end
+
+ def test_column
+ column = PostgresqlPoint.columns_hash["x"]
+ assert_equal :point, column.type
+ assert_equal "point", column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_default
+ column = PostgresqlPoint.columns_hash["y"]
+ assert_equal [12.2, 13.3], column.default
+ assert_equal [12.2, 13.3], PostgresqlPoint.new.y
+
+ column = PostgresqlPoint.columns_hash["z"]
+ assert_equal [14.4, 15.5], column.default
+ assert_equal [14.4, 15.5], PostgresqlPoint.new.z
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("postgresql_points")
+ assert_match %r{t\.point\s+"x"$}, output
+ assert_match %r{t\.point\s+"y",\s+default: \[12\.2, 13\.3\]$}, output
+ assert_match %r{t\.point\s+"z",\s+default: \[14\.4, 15\.5\]$}, output
+ end
+
+ def test_roundtrip
+ PostgresqlPoint.create! x: [10, 25.2]
+ record = PostgresqlPoint.first
+ assert_equal [10, 25.2], record.x
+
+ record.x = [1.1, 2.2]
+ record.save!
+ assert record.reload
+ assert_equal [1.1, 2.2], record.x
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index c24c4b0d56..83b495d600 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
@@ -106,6 +106,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def test_cast_value_on_write
x = Hstore.new tags: {"bool" => true, "number" => 5}
+ assert_equal({"bool" => true, "number" => 5}, x.tags_before_type_cast)
assert_equal({"bool" => "true", "number" => "5"}, x.tags)
x.save
assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags)
@@ -117,11 +118,11 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
data = "\"1\"=>\"2\""
hash = @column.class.string_to_hstore data
assert_equal({'1' => '2'}, hash)
- assert_equal({'1' => '2'}, @column.type_cast(data))
+ assert_equal({'1' => '2'}, @column.type_cast_from_database(data))
- assert_equal({}, @column.type_cast(""))
- assert_equal({'key'=>nil}, @column.type_cast('key => NULL'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b")))
+ assert_equal({}, @column.type_cast_from_database(""))
+ assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b")))
end
def test_with_store_accessors
@@ -142,6 +143,35 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_equal "GMT", x.timezone
end
+ def test_duplication_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
+
+ y = x.dup
+ assert_equal "fr", y.language
+ assert_equal "GMT", y.timezone
+ end
+
+ def test_yaml_round_trip_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
+
+ y = YAML.load(YAML.dump(x))
+ assert_equal "fr", y.language
+ assert_equal "GMT", y.timezone
+ end
+
+ def test_changes_in_place
+ hstore = Hstore.create!(settings: { 'one' => 'two' })
+ hstore.settings['three'] = 'four'
+ hstore.save!
+ hstore.reload
+
+ assert_equal 'four', hstore.settings['three']
+ end
+
def test_gen1
assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''}))
end
@@ -159,31 +189,31 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
end
def test_parse1
- assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
end
def test_parse2
- assert_equal({" " => " "}, @column.type_cast("\\ =>\\ "))
+ assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ "))
end
def test_parse3
- assert_equal({"=" => ">"}, @column.type_cast("==>>"))
+ assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>"))
end
def test_parse4
- assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w'))
+ assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w'))
end
def test_parse5
- assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w'))
+ assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w'))
end
def test_parse6
- assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w'))
+ assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w'))
end
def test_parse7
- assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w'))
+ assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w'))
end
def test_rewrite
@@ -274,6 +304,34 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
Hstore.update_all tags: { }
assert_equal({ }, hstore.reload.tags)
end
+
+ class TagCollection
+ def initialize(hash); @hash = hash end
+ def to_hash; @hash end
+ def self.load(hash); new(hash) end
+ def self.dump(object); object.to_hash end
+ end
+
+ class HstoreWithSerialize < Hstore
+ serialize :tags, TagCollection
+ end
+
+ def test_hstore_with_serialized_attributes
+ HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"})
+ record = HstoreWithSerialize.first
+ assert_instance_of TagCollection, record.tags
+ assert_equal({"one" => "two"}, record.tags.to_hash)
+ record.tags = TagCollection.new("three" => "four")
+ record.save!
+ assert_equal({"three" => "four"}, HstoreWithSerialize.first.tags.to_hash)
+ end
+
+ def test_clone_hstore_with_serialized_attributes
+ HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"})
+ record = HstoreWithSerialize.first
+ dupe = record.dup
+ assert_equal({"one" => "two"}, dupe.tags.to_hash)
+ end
end
private
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index ee793ffff2..a3400a5a19 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
@@ -68,6 +68,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
def test_cast_value_on_write
x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar}
+ assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast)
assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload)
x.save
assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload)
@@ -79,11 +80,11 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
data = "{\"a_key\":\"a_value\"}"
hash = column.class.string_to_json data
assert_equal({'a_key' => 'a_value'}, hash)
- assert_equal({'a_key' => 'a_value'}, column.type_cast(data))
+ assert_equal({'a_key' => 'a_value'}, column.type_cast_from_database(data))
- assert_equal({}, column.type_cast("{}"))
- assert_equal({'key'=>nil}, column.type_cast('{"key": null}'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"})))
+ assert_equal({}, column.type_cast_from_database("{}"))
+ assert_equal({'key'=>nil}, column.type_cast_from_database('{"key": null}'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast_from_database(%q({"c":"}", "\"a\"":"b \"a b"})))
end
def test_rewrite
@@ -139,6 +140,22 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
assert_equal "640×1136", x.resolution
end
+ def test_duplication_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = x.dup
+ assert_equal "320×480", y.resolution
+ end
+
+ def test_yaml_round_trip_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ y = YAML.load(YAML.dump(x))
+ assert_equal "320×480", y.resolution
+ end
+
def test_update_all
json = JsonDataType.create! payload: { "one" => "two" }
@@ -148,4 +165,24 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
JsonDataType.update_all payload: { }
assert_equal({ }, json.reload.payload)
end
+
+ def test_changes_in_place
+ json = JsonDataType.new
+ assert_not json.changed?
+
+ json.payload = { 'one' => 'two' }
+ assert json.changed?
+ assert json.payload_changed?
+
+ json.save!
+ assert_not json.changed?
+
+ json.payload['three'] = 'four'
+ assert json.payload_changed?
+
+ json.save!
+ json.reload
+
+ assert json.payload['three'] = 'four'
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 718f37a380..ddb7cd658c 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -1,7 +1,5 @@
# encoding: utf-8
require "cases/helper"
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlLtreeTest < ActiveRecord::TestCase
class Ltree < ActiveRecord::Base
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..bdfeedafab
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+require "cases/helper"
+require 'support/schema_dumping_helper'
+
+class PostgresqlMoneyTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ class PostgresqlMoney < ActiveRecord::Base; end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.execute("set lc_monetary = 'C'")
+ @connection.create_table('postgresql_moneys') do |t|
+ t.column "wealth", "money"
+ t.column "depth", "money", default: "150.55"
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_moneys'
+ end
+
+ def test_column
+ column = PostgresqlMoney.columns_hash["wealth"]
+ assert_equal :money, 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_default
+ column = PostgresqlMoney.columns_hash["depth"]
+ assert_equal BigDecimal.new("150.55"), column.default
+ assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth
+ 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_from_user("$12,345,678.12"))
+ assert_equal(12345678.12, column.type_cast_from_user("$12.345.678,12"))
+ assert_equal(-1.15, column.type_cast_from_user("-$1.15"))
+ assert_equal(-2.25, column.type_cast_from_user("($2.25)"))
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("postgresql_moneys")
+ assert_match %r{t\.money\s+"wealth",\s+scale: 2$}, output
+ assert_match %r{t\.money\s+"depth",\s+scale: 2,\s+default: 150.55$}, output
+ 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/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
new file mode 100644
index 0000000000..32085cbb17
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+require "cases/helper"
+
+class PostgresqlNetworkTest < ActiveRecord::TestCase
+ class PostgresqlNetworkAddress < ActiveRecord::Base
+ end
+
+ def test_cidr_column
+ column = PostgresqlNetworkAddress.columns_hash["cidr_address"]
+ assert_equal :cidr, column.type
+ assert_equal "cidr", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_inet_column
+ column = PostgresqlNetworkAddress.columns_hash["inet_address"]
+ assert_equal :inet, column.type
+ assert_equal "inet", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_macaddr_column
+ column = PostgresqlNetworkAddress.columns_hash["mac_address"]
+ assert_equal :macaddr, column.type
+ assert_equal "macaddr", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_network_types
+ PostgresqlNetworkAddress.create(cidr_address: '192.168.0.0/24',
+ inet_address: '172.16.1.254/32',
+ mac_address: '01:23:45:67:89:0a')
+
+ address = PostgresqlNetworkAddress.first
+ assert_equal IPAddr.new('192.168.0.0/24'), address.cidr_address
+ assert_equal IPAddr.new('172.16.1.254'), address.inet_address
+ assert_equal '01:23:45:67:89:0a', address.mac_address
+
+ address.cidr_address = '10.1.2.3/32'
+ address.inet_address = '10.0.0.0/8'
+ address.mac_address = 'bc:de:f0:12:34:56'
+
+ address.save!
+ assert address.reload
+ assert_equal IPAddr.new('10.1.2.3/32'), address.cidr_address
+ assert_equal IPAddr.new('10.0.0.0/8'), address.inet_address
+ assert_equal 'bc:de:f0:12:34:56', address.mac_address
+ end
+
+ def test_invalid_network_address
+ invalid_address = PostgresqlNetworkAddress.new(cidr_address: 'invalid addr',
+ inet_address: 'invalid addr')
+ assert_nil invalid_address.cidr_address
+ assert_nil invalid_address.inet_address
+ assert_equal 'invalid addr', invalid_address.cidr_address_before_type_cast
+ assert_equal 'invalid addr', invalid_address.inet_address_before_type_cast
+ assert invalid_address.save
+
+ invalid_address.reload
+ assert_nil invalid_address.cidr_address
+ assert_nil invalid_address.inet_address
+ assert_nil invalid_address.cidr_address_before_type_cast
+ assert_nil invalid_address.inet_address_before_type_cast
+ 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 b7791078db..cfff1f980b 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -134,18 +134,18 @@ module ActiveRecord
end
def test_default_sequence_name
- assert_equal 'accounts_id_seq',
+ assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'),
@connection.default_sequence_name('accounts', 'id')
- assert_equal 'accounts_id_seq',
+ assert_equal PostgreSQL::Name.new('public', 'accounts_id_seq'),
@connection.default_sequence_name('accounts')
end
def test_default_sequence_name_bad_table
- assert_equal 'zomg_id_seq',
+ assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'),
@connection.default_sequence_name('zomg', 'id')
- assert_equal 'zomg_id_seq',
+ assert_equal PostgreSQL::Name.new(nil, 'zomg_id_seq'),
@connection.default_sequence_name('zomg')
end
@@ -216,7 +216,7 @@ module ActiveRecord
)
seq = @connection.pk_and_sequence_for('ex').last
- assert_equal 'ex_id_seq', seq
+ assert_equal PostgreSQL::Name.new("public", "ex_id_seq"), seq
@connection.exec_query(
"DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
@@ -353,6 +353,17 @@ module ActiveRecord
assert_equal "posts.title, posts.updater_id AS alias_0", @connection.columns_for_distinct("posts.title", ["posts.updater_id desc nulls last"])
end
+ def test_columns_for_distinct_without_order_specifiers
+ assert_equal "posts.title, posts.updater_id AS alias_0",
+ @connection.columns_for_distinct("posts.title", ["posts.updater_id"])
+
+ assert_equal "posts.title, posts.updater_id AS alias_0",
+ @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls last"])
+
+ assert_equal "posts.title, posts.updater_id AS alias_0",
+ @connection.columns_for_distinct("posts.title", ["posts.updater_id nulls first"])
+ end
+
def test_raise_error_when_cannot_translate_exception
assert_raise TypeError do
@connection.send(:log, nil) { @connection.execute(nil) }
@@ -396,6 +407,23 @@ module ActiveRecord
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 060b17d071..0f6e39322c 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -1,7 +1,5 @@
require "cases/helper"
require 'support/connection_helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
if ActiveRecord::Base.connection.supports_ranges?
class PostgresqlRange < ActiveRecord::Base
@@ -158,7 +156,7 @@ _SQL
assert_equal 0.5..0.7, @first_range.float_range
assert_equal 0.5...0.7, @second_range.float_range
assert_equal 0.5...Float::INFINITY, @third_range.float_range
- assert_equal (-Float::INFINITY...Float::INFINITY), @fourth_range.float_range
+ assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.float_range)
assert_nil @empty_range.float_range
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 11ec7599a3..9e5fd17dc4 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.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"
@@ -305,14 +331,15 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_pk_and_sequence_for_with_schema_specified
+ pg_name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
[
%("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
%("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
].each do |given|
pk, seq = @connection.pk_and_sequence_for(given)
assert_equal 'id', pk, "primary key should be found when table referenced as #{given}"
- assert_equal "#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
- assert_equal "#{UNMATCHED_SEQUENCE_NAME}", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
+ assert_equal pg_name.new(SCHEMA_NAME, "#{PK_TABLE_NAME}_id_seq"), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
+ assert_equal pg_name.new(SCHEMA_NAME, UNMATCHED_SEQUENCE_NAME), seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
end
end
@@ -352,6 +379,14 @@ class SchemaTest < ActiveRecord::TestCase
end
end
+ def test_reset_pk_sequence
+ sequence_name = "#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
+ @connection.execute "SELECT setval('#{sequence_name}', 123)"
+ assert_equal "124", @connection.select_value("SELECT nextval('#{sequence_name}')")
+ @connection.reset_pk_sequence!("#{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME}")
+ assert_equal "1", @connection.select_value("SELECT nextval('#{sequence_name}')")
+ end
+
private
def columns(table_name)
@connection.send(:column_definitions, table_name).map do |name, type, default|
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index 4d29a20e66..3614b29190 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -2,6 +2,47 @@ require 'cases/helper'
require 'models/developer'
require 'models/topic'
+class PostgresqlTimestampTest < ActiveRecord::TestCase
+ class PostgresqlTimestampWithZone < ActiveRecord::Base; end
+
+ self.use_transactional_fixtures = false
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')")
+ end
+
+ teardown do
+ PostgresqlTimestampWithZone.delete_all
+ end
+
+ def test_timestamp_with_zone_values_with_rails_time_zone_support
+ with_timezone_config default: :utc, aware_attributes: true do
+ @connection.reconnect!
+
+ timestamp = PostgresqlTimestampWithZone.find(1)
+ assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time
+ assert_instance_of Time, timestamp.time
+ end
+ ensure
+ @connection.reconnect!
+ end
+
+ def test_timestamp_with_zone_values_without_rails_time_zone_support
+ with_timezone_config default: :local, aware_attributes: false do
+ @connection.reconnect!
+ # make sure to use a non-UTC time zone
+ @connection.execute("SET time zone 'America/Jamaica'", 'SCHEMA')
+
+ timestamp = PostgresqlTimestampWithZone.find(1)
+ assert_equal Time.utc(2010,1,1, 11,0,0), timestamp.time
+ assert_instance_of Time, timestamp.time
+ end
+ ensure
+ @connection.reconnect!
+ end
+end
+
class TimestampTest < ActiveRecord::TestCase
fixtures :topics
@@ -82,20 +123,32 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal date, Developer.find_by_name("aaron").updated_at
end
+ def test_bc_timestamp_leap_year
+ date = Time.utc(-4, 2, 29)
+ Developer.create!(:name => "taihou", :updated_at => date)
+ assert_equal date, Developer.find_by_name("taihou").updated_at
+ end
+
+ def test_bc_timestamp_year_zero
+ date = Time.utc(0, 4, 7)
+ Developer.create!(:name => "yahagi", :updated_at => date)
+ assert_equal date, Developer.find_by_name("yahagi").updated_at
+ end
+
private
- def pg_datetime_precision(table_name, column_name)
- results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'")
- result = results.find do |result_hash|
- result_hash["column_name"] == column_name
- end
- result && result["datetime_precision"]
+ def pg_datetime_precision(table_name, column_name)
+ results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'")
+ result = results.find do |result_hash|
+ result_hash["column_name"] == column_name
end
+ result && result["datetime_precision"]
+ end
- def activerecord_column_option(tablename, column_name, option)
- result = ActiveRecord::Base.connection.columns(tablename).find do |column|
- column.name == column_name
- end
- result && result.send(option)
+ def activerecord_column_option(tablename, column_name, option)
+ result = ActiveRecord::Base.connection.columns(tablename).find do |column|
+ column.name == column_name
end
+ result && result.send(option)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
index 9e7b08ef34..3fdb6888d9 100644
--- a/activerecord/test/cases/adapters/postgresql/utils_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -1,9 +1,10 @@
require 'cases/helper'
class PostgreSQLUtilsTest < ActiveSupport::TestCase
- include ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils
+ Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
+ include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
- def test_extract_schema_and_table
+ def test_extract_schema_qualified_name
{
%(table_name) => [nil,'table_name'],
%("table.name") => [nil,'table.name'],
@@ -14,7 +15,47 @@ class PostgreSQLUtilsTest < ActiveSupport::TestCase
%("even spaces".table) => ['even spaces','table'],
%(schema."table.name") => ['schema', 'table.name']
}.each do |given, expect|
- assert_equal expect, extract_schema_and_table(given)
+ assert_equal Name.new(*expect), extract_schema_qualified_name(given)
end
end
end
+
+class PostgreSQLNameTest < ActiveSupport::TestCase
+ Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
+
+ test "represents itself as schema.name" do
+ obj = Name.new("public", "articles")
+ assert_equal "public.articles", obj.to_s
+ end
+
+ test "without schema, represents itself as name only" do
+ obj = Name.new(nil, "articles")
+ assert_equal "articles", obj.to_s
+ end
+
+ test "quoted returns a string representation usable in a query" do
+ assert_equal %("articles"), Name.new(nil, "articles").quoted
+ assert_equal %("public"."articles"), Name.new("public", "articles").quoted
+ end
+
+ test "prevents double quoting" do
+ name = Name.new('"quoted_schema"', '"quoted_table"')
+ assert_equal "quoted_schema.quoted_table", name.to_s
+ assert_equal %("quoted_schema"."quoted_table"), name.quoted
+ end
+
+ test "equality based on state" do
+ assert_equal Name.new("access", "users"), Name.new("access", "users")
+ assert_equal Name.new(nil, "users"), Name.new(nil, "users")
+ assert_not_equal Name.new(nil, "users"), Name.new("access", "users")
+ assert_not_equal Name.new("access", "users"), Name.new("public", "users")
+ assert_not_equal Name.new("public", "users"), Name.new("public", "articles")
+ end
+
+ test "can be used as hash key" do
+ hash = {Name.new("schema", "article_seq") => "success"}
+ assert_equal "success", hash[Name.new("schema", "article_seq")]
+ assert_equal nil, hash[Name.new("schema", "articles")]
+ assert_equal nil, hash[Name.new("public", "article_seq")]
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index bdf8e15e3e..40ed0f64a4 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -43,14 +43,16 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
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.find { |c| c.name == 'thingy' }
+ 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.find { |c| c.name == 'thingy' }
+ 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
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index ae299697b1..48c6eeb62c 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -1,8 +1,5 @@
# encoding: utf-8
-
require 'cases/helper'
-require 'active_record/base'
-require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlXMLTest < ActiveRecord::TestCase
class XmlDataType < ActiveRecord::Base
@@ -20,7 +17,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/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index b478db749d..13b754d226 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -60,7 +60,6 @@ class CopyTableTest < ActiveRecord::TestCase
assert_equal original_id.type, copied_id.type
assert_equal original_id.sql_type, copied_id.sql_type
assert_equal original_id.limit, copied_id.limit
- assert_equal original_id.primary, copied_id.primary
end
end
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..8c9a051eea 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -15,10 +15,10 @@ module ActiveRecord
def test_type_cast_binary_encoding_without_logger
@conn.extend(Module.new { def logger; end })
- column = Struct.new(:type, :name).new(:string, "foo")
+ cast_type = Type::String.new
binary = SecureRandom.hex
expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary, column)
+ assert_equal expected, @conn.type_cast(binary, cast_type)
end
def test_type_cast_symbol
@@ -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
@@ -84,7 +84,7 @@ module ActiveRecord
assert_raise(TypeError) { @conn.type_cast(obj, nil) }
end
- def test_quoted_id
+ def test_type_cast_object_which_responds_to_quoted_id
quoted_id_obj = Class.new {
def quoted_id
"'zomg'"
@@ -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/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 811695938e..8700b20dee 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -17,6 +17,20 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::SchemaMigration.delete_all rescue nil
end
+ def test_has_no_primary_key
+ old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
+ ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
+ assert_nil ActiveRecord::SchemaMigration.primary_key
+
+ ActiveRecord::SchemaMigration.create_table
+ assert_difference "ActiveRecord::SchemaMigration.count", 1 do
+ ActiveRecord::SchemaMigration.create version: 12
+ end
+ ensure
+ ActiveRecord::SchemaMigration.drop_table
+ ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type
+ end
+
def test_schema_define
ActiveRecord::Schema.define(:version => 7) do
create_table :fruits do |t|
@@ -34,6 +48,7 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_schema_define_w_table_name_prefix
table_name = ActiveRecord::SchemaMigration.table_name
+ old_table_name_prefix = ActiveRecord::Base.table_name_prefix
ActiveRecord::Base.table_name_prefix = "nep_"
ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}"
ActiveRecord::Schema.define(:version => 7) do
@@ -46,7 +61,7 @@ if ActiveRecord::Base.connection.supports_migrations?
end
assert_equal 7, ActiveRecord::Migrator::current_version
ensure
- ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_prefix = old_table_name_prefix
ActiveRecord::SchemaMigration.table_name = table_name
end
@@ -66,5 +81,12 @@ if ActiveRecord::Base.connection.supports_migrations?
end
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
end
+
+ def test_normalize_version
+ assert_equal "118", ActiveRecord::SchemaMigration.normalize_migration_number("0000118")
+ assert_equal "002", ActiveRecord::SchemaMigration.normalize_migration_number("2")
+ assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017")
+ assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947")
+ end
end
end
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 3b484a0d64..9c92dc1141 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -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])
@@ -563,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 7eaa5adc86..910067666a 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
@@ -1177,6 +1167,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
)
end
+ test "deep preload" do
+ post = Post.preload(author: :posts, comments: :post).first
+
+ assert_predicate post.author.association(:posts), :loaded?
+ assert_predicate post.comments.first.association(:post), :loaded?
+ end
+
test "preloading does not cache has many association subset when preloaded with a through association" do
author = Author.includes(:comments_with_order_and_conditions, :posts).first
assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size }
@@ -1206,6 +1203,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ test "preloading the same association twice works" do
+ Member.create!
+ members = Member.preload(:current_membership).includes(current_membership: :club).all.to_a
+ assert_no_queries {
+ members_with_membership = members.select(&:current_membership)
+ assert_equal 3, members_with_membership.map(&:current_membership).map(&:club).size
+ }
+ end
+
test "preloading with a polymorphic association and using the existential predicate" do
assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
@@ -1232,4 +1238,27 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 2, author.posts.size
}
end
+
+ test "including association based on sql condition and no database column" do
+ assert_equal pets(:parrot), Owner.including_last_pet.first.last_pet
+ 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..080c499444 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
@@ -66,6 +70,14 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base
:foreign_key => "developer_id"
end
+class SubDeveloper < Developer
+ self.table_name = 'developers'
+ has_and_belongs_to_many :special_projects,
+ :join_table => 'developers_projects',
+ :foreign_key => "project_id",
+ :association_foreign_key => "developer_id"
+end
+
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings
@@ -82,6 +94,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 +234,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") }
@@ -786,7 +822,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], Pirate.where(id: redbeard.id)
end
- test "has and belongs to many associations on new records use null relations" do
+ def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations
projects = Developer.new.projects
assert_no_queries do
assert_equal [], projects
@@ -819,4 +855,23 @@ 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
+
+ def test_redefine_habtm
+ child = SubDeveloper.new("name" => "Aredridel")
+ child.special_projects << SpecialProject.new("name" => "Special Project")
+ assert child.save, 'child object should be saved'
+ 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 6675e19dd9..8641584c0c 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -330,6 +330,19 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert post.single_people.include?(person)
end
+ def test_both_parent_ids_set_when_saving_new
+ post = Post.new(title: 'Hello', body: 'world')
+ person = Person.new(first_name: 'Sean')
+
+ post.people = [person]
+ post.save
+
+ assert post.id
+ assert person.id
+ assert_equal post.id, post.readers.first.post_id
+ assert_equal person.id, post.readers.first.person_id
+ end
+
def test_delete_association
assert_queries(2){posts(:welcome);people(:michael); }
@@ -698,9 +711,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 +1092,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 +1104,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
@@ -1102,11 +1112,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [posts(:thinking)], person.reload.first_posts
end
- test "has many through with includes in through association scope" do
+ def test_has_many_through_with_includes_in_through_association_scope
assert_not_empty posts(:welcome).author_address_extra_with_address
end
- test "insert records via has_many_through association with scope" do
+ def test_insert_records_via_has_many_through_association_with_scope
club = Club.create!
member = Member.create!
Membership.create!(club: club, member: member)
@@ -1117,4 +1127,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
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/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index a2725441b3..089cb0a3a2 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -45,6 +45,20 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_equal clubs(:moustache_club), new_member.club
end
+ def test_creating_association_sets_both_parent_ids_for_new
+ member = Member.new(name: 'Sean Griffin')
+ club = Club.new(name: 'Da Club')
+
+ member.club = club
+
+ member.save!
+
+ assert member.id
+ assert club.id
+ assert_equal member.id, member.current_membership.member_id
+ assert_equal club.id, member.current_membership.club_id
+ end
+
def test_replace_target_record
new_club = Club.create(:name => "Marx Bros")
@member.club = new_club
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index b23517b2f9..07cf65a760 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -126,4 +126,14 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
categories = author.categories.includes(:special_categorizations).references(:special_categorizations).to_a
assert_equal 2, categories.size
end
+
+ test "the correct records are loaded when including an aliased association" do
+ author = Author.create! name: "Jon"
+ author.categories.create! name: 'Not Special'
+ author.special_categories.create! name: 'Special'
+
+ categories = author.categories.eager_load(:special_categorizations).order(:name).to_a
+ assert_equal 0, categories.first.special_categorizations.size
+ assert_equal 1, categories.second.special_categorizations.size
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 893030345f..60df4e14dd 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -100,6 +100,17 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
assert_respond_to club_reflection, :has_inverse?
assert !club_reflection.has_inverse?, "A has_many_through association should not find an inverse automatically"
end
+
+ def test_polymorphic_relationships_should_still_not_have_inverses_when_non_polymorphic_relationship_has_the_same_name
+ man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse)
+ face_reflection = Face.reflect_on_association(:man)
+
+ assert_respond_to face_reflection, :has_inverse?
+ assert face_reflection.has_inverse?, "For this test, the non-polymorphic association must have an inverse"
+
+ assert_respond_to man_reflection, :has_inverse?
+ assert !man_reflection.has_inverse?, "The target of a polymorphic association should not find an inverse automatically"
+ end
end
class InverseAssociationTests < ActiveRecord::TestCase
@@ -333,7 +344,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
def test_parent_instance_should_be_shared_within_create_block_of_new_child
man = Man.first
- interest = man.interests.build do |i|
+ interest = man.interests.create do |i|
assert i.man.equal?(man), "Man of child should be the same instance as a parent"
end
assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
new file mode 100644
index 0000000000..35393753a2
--- /dev/null
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -0,0 +1,114 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class AttributeDecoratorsTest < ActiveRecord::TestCase
+ class Model < ActiveRecord::Base
+ self.table_name = 'attribute_decorators_model'
+ end
+
+ class StringDecorator < SimpleDelegator
+ def initialize(delegate, decoration = "decorated!")
+ @decoration = decoration
+ super(delegate)
+ end
+
+ def type_cast_from_user(value)
+ "#{super} #{@decoration}"
+ end
+
+ alias type_cast_from_database type_cast_from_user
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :attribute_decorators_model, force: true do |t|
+ t.string :a_string
+ end
+ end
+
+ teardown do
+ return unless @connection
+ @connection.execute 'DROP TABLE IF EXISTS attribute_decorators_model'
+ Model.attribute_type_decorations.clear
+ Model.reset_column_information
+ end
+
+ test "attributes can be decorated" do
+ model = Model.new(a_string: 'Hello')
+ assert_equal 'Hello', model.a_string
+
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+
+ model = Model.new(a_string: 'Hello')
+ assert_equal 'Hello decorated!', model.a_string
+ end
+
+ test "decoration does not eagerly load existing columns" do
+ assert_no_queries do
+ Model.reset_column_information
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+ end
+ end
+
+ test "undecorated columns are not touched" do
+ Model.attribute :another_string, Type::String.new, default: 'something or other'
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+
+ assert_equal 'something or other', Model.new.another_string
+ end
+
+ test "decorators can be chained" do
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+ Model.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) }
+
+ model = Model.new(a_string: 'Hello!')
+
+ assert_equal 'Hello! decorated! decorated!', model.a_string
+ end
+
+ test "decoration of the same type multiple times is idempotent" do
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+
+ model = Model.new(a_string: 'Hello')
+ assert_equal 'Hello decorated!', model.a_string
+ end
+
+ test "decorations occur in order of declaration" do
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+ Model.decorate_attribute_type(:a_string, :other) do |type|
+ StringDecorator.new(type, 'decorated again!')
+ end
+
+ model = Model.new(a_string: 'Hello!')
+
+ assert_equal 'Hello! decorated! decorated again!', model.a_string
+ end
+
+ test "decorating attributes does not modify parent classes" do
+ Model.attribute :another_string, Type::String.new, default: 'whatever'
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+ child_class = Class.new(Model)
+ child_class.decorate_attribute_type(:another_string, :test) { |t| StringDecorator.new(t) }
+ child_class.decorate_attribute_type(:a_string, :other) { |t| StringDecorator.new(t) }
+
+ model = Model.new(a_string: 'Hello!')
+ child = child_class.new(a_string: 'Hello!')
+
+ assert_equal 'Hello! decorated!', model.a_string
+ assert_equal 'whatever', model.another_string
+ assert_equal 'Hello! decorated! decorated!', child.a_string
+ # We are round tripping the default, and we don't undo our decoration
+ assert_equal 'whatever decorated! decorated!', child.another_string
+ end
+
+ test "defaults are decorated on the column" do
+ Model.attribute :a_string, Type::String.new, default: 'whatever'
+ Model.decorate_attribute_type(:a_string, :test) { |t| StringDecorator.new(t) }
+
+ column = Model.columns_hash['a_string']
+
+ assert_equal 'whatever decorated!', column.default
+ end
+ end
+end
diff --git a/activerecord/test/cases/attribute_methods/serialization_test.rb b/activerecord/test/cases/attribute_methods/serialization_test.rb
deleted file mode 100644
index 75de773961..0000000000
--- a/activerecord/test/cases/attribute_methods/serialization_test.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- module AttributeMethods
- class SerializationTest < ActiveSupport::TestCase
- class FakeColumn < Struct.new(:name)
- def type; :integer; end
- def type_cast(s); "#{s}!"; end
- end
-
- class NullCoder
- def load(v); v; end
- end
-
- def test_type_cast_serialized_value
- value = Serialization::Attribute.new(NullCoder.new, "Hello world", :serialized)
- type = Serialization::Type.new(FakeColumn.new)
- assert_equal "Hello world!", type.type_cast(value)
- end
-
- def test_type_cast_unserialized_value
- value = Serialization::Attribute.new(nil, "Hello world", :unserialized)
- type = Serialization::Type.new(FakeColumn.new)
- type.type_cast(value)
- assert_equal "Hello world", type.type_cast(value)
- end
- end
- end
-end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 38e93288e4..f832ca3451 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -299,6 +299,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
computer = Computer.select('id').first
assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] }
assert_raises(ActiveModel::MissingAttributeError) { computer[:extendedWarranty] }
+ assert_raises(ActiveModel::MissingAttributeError) { computer[:no_column_exists] = 'Hello!' }
+ assert_nothing_raised { computer[:developer] = 'Hello!' }
end
def test_read_attribute_when_false
@@ -449,10 +451,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_declared_suffixed_attribute_method_affects_respond_to_and_method_missing
- topic = @target.new(:title => 'Budget')
%w(_default _title_default _it! _candidate= able?).each do |suffix|
@target.class_eval "def attribute#{suffix}(*args) args end"
@target.attribute_method_suffix suffix
+ topic = @target.new(:title => 'Budget')
meth = "title#{suffix}"
assert topic.respond_to?(meth)
@@ -463,10 +465,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_declared_affixed_attribute_method_affects_respond_to_and_method_missing
- topic = @target.new(:title => 'Budget')
[['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix|
@target.class_eval "def #{prefix}attribute#{suffix}(*args) args end"
@target.attribute_method_affix({ :prefix => prefix, :suffix => suffix })
+ topic = @target.new(:title => 'Budget')
meth = "#{prefix}title#{suffix}"
assert topic.respond_to?(meth)
@@ -515,43 +517,17 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_only_time_related_columns_are_meant_to_be_cached_by_default
- expected = %w(datetime timestamp time date).sort
- assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort
- end
-
- def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default
- default_attributes = Topic.cached_attributes
- Topic.cache_attributes :replies_count
- expected = default_attributes + ["replies_count"]
- assert_equal expected.sort, Topic.cached_attributes.sort
- Topic.instance_variable_set "@cached_attributes", nil
- end
-
- def test_cacheable_columns_are_actually_cached
- assert_equal cached_columns.sort, Topic.cached_attributes.sort
- end
-
- def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else
- t = topics(:first)
- cache = t.instance_variable_get "@attributes_cache"
-
- assert_not_nil cache
- assert cache.empty?
+ def test_deprecated_cache_attributes
+ assert_deprecated do
+ Topic.cache_attributes :replies_count
+ end
- all_columns = Topic.columns.map(&:name)
- uncached_columns = all_columns - cached_columns
+ assert_deprecated do
+ Topic.cached_attributes
+ end
- all_columns.each do |attr_name|
- attribute_gets_cached = Topic.cache_attribute?(attr_name)
- val = t.send attr_name unless attr_name == "type"
- if attribute_gets_cached
- assert cached_columns.include?(attr_name)
- assert_equal val, cache[attr_name]
- else
- assert uncached_columns.include?(attr_name)
- assert !cache.include?(attr_name)
- end
+ assert_deprecated do
+ Topic.cache_attribute? :replies_count
end
end
@@ -843,6 +819,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal !real_topic.title?, klass.find(real_topic.id).title?
end
+ def test_calling_super_when_parent_does_not_define_method_raises_error
+ klass = new_topic_like_ar_class do
+ def some_method_that_is_not_on_super
+ super
+ end
+ end
+
+ assert_raise(NoMethodError) do
+ klass.new.some_method_that_is_not_on_super
+ end
+ end
+
private
def new_topic_like_ar_class(&block)
@@ -856,7 +844,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def cached_columns
- Topic.columns.map(&:name) - Topic.serialized_attributes.keys
+ Topic.columns.map(&:name)
end
def time_related_columns_on_topic
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
new file mode 100644
index 0000000000..57dd2e9a5e
--- /dev/null
+++ b/activerecord/test/cases/attribute_test.rb
@@ -0,0 +1,103 @@
+require 'cases/helper'
+require 'minitest/mock'
+
+module ActiveRecord
+ class AttributeTest < ActiveRecord::TestCase
+ setup do
+ @type = MiniTest::Mock.new
+ end
+
+ teardown do
+ assert @type.verify
+ end
+
+ test "from_database + read type casts from database" do
+ @type.expect(:type_cast_from_database, 'type cast from database', ['a value'])
+ attribute = Attribute.from_database('a value', @type)
+
+ type_cast_value = attribute.value
+
+ assert_equal 'type cast from database', type_cast_value
+ end
+
+ test "from_user + read type casts from user" do
+ @type.expect(:type_cast_from_user, 'type cast from user', ['a value'])
+ attribute = Attribute.from_user('a value', @type)
+
+ type_cast_value = attribute.value
+
+ assert_equal 'type cast from user', type_cast_value
+ end
+
+ test "reading memoizes the value" do
+ @type.expect(:type_cast_from_database, 'from the database', ['whatever'])
+ attribute = Attribute.from_database('whatever', @type)
+
+ type_cast_value = attribute.value
+ second_read = attribute.value
+
+ assert_equal 'from the database', type_cast_value
+ assert_same type_cast_value, second_read
+ end
+
+ test "reading memoizes falsy values" do
+ @type.expect(:type_cast_from_database, false, ['whatever'])
+ attribute = Attribute.from_database('whatever', @type)
+
+ attribute.value
+ attribute.value
+ end
+
+ test "read_before_typecast returns the given value" do
+ attribute = Attribute.from_database('raw value', @type)
+
+ raw_value = attribute.value_before_type_cast
+
+ assert_equal 'raw value', raw_value
+ end
+
+ test "from_database + read_for_database type casts to and from database" do
+ @type.expect(:type_cast_from_database, 'read from database', ['whatever'])
+ @type.expect(:type_cast_for_database, 'ready for database', ['read from database'])
+ attribute = Attribute.from_database('whatever', @type)
+
+ type_cast_for_database = attribute.value_for_database
+
+ assert_equal 'ready for database', type_cast_for_database
+ end
+
+ test "from_user + read_for_database type casts from the user to the database" do
+ @type.expect(:type_cast_from_user, 'read from user', ['whatever'])
+ @type.expect(:type_cast_for_database, 'ready for database', ['read from user'])
+ attribute = Attribute.from_user('whatever', @type)
+
+ type_cast_for_database = attribute.value_for_database
+
+ assert_equal 'ready for database', type_cast_for_database
+ end
+
+ test "duping dups the value" do
+ @type.expect(:type_cast_from_database, 'type cast', ['a value'])
+ attribute = Attribute.from_database('a value', @type)
+
+ value_from_orig = attribute.value
+ value_from_clone = attribute.dup.value
+ value_from_orig << ' foo'
+
+ assert_equal 'type cast foo', value_from_orig
+ assert_equal 'type cast', value_from_clone
+ end
+
+ test "duping does not dup the value if it is not dupable" do
+ @type.expect(:type_cast_from_database, false, ['a value'])
+ attribute = Attribute.from_database('a value', @type)
+
+ assert_same attribute.value, attribute.dup.value
+ end
+
+ test "duping does not eagerly type cast if we have not yet type cast" do
+ attribute = Attribute.from_database('a value', @type)
+ attribute.dup
+ end
+ end
+end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
new file mode 100644
index 0000000000..79ef0502cb
--- /dev/null
+++ b/activerecord/test/cases/attributes_test.rb
@@ -0,0 +1,111 @@
+require 'cases/helper'
+
+class OverloadedType < ActiveRecord::Base
+ attribute :overloaded_float, Type::Integer.new
+ attribute :overloaded_string_with_limit, Type::String.new(limit: 50)
+ attribute :non_existent_decimal, Type::Decimal.new
+ attribute :string_with_default, Type::String.new, default: 'the overloaded default'
+end
+
+class ChildOfOverloadedType < OverloadedType
+end
+
+class GrandchildOfOverloadedType < ChildOfOverloadedType
+ attribute :overloaded_float, Type::Float.new
+end
+
+class UnoverloadedType < ActiveRecord::Base
+ self.table_name = 'overloaded_types'
+end
+
+module ActiveRecord
+ class CustomPropertiesTest < ActiveRecord::TestCase
+ def test_overloading_types
+ data = OverloadedType.new
+
+ data.overloaded_float = "1.1"
+ data.unoverloaded_float = "1.1"
+
+ assert_equal 1, data.overloaded_float
+ assert_equal 1.1, data.unoverloaded_float
+ end
+
+ def test_overloaded_properties_save
+ data = OverloadedType.new
+
+ data.overloaded_float = "2.2"
+ data.save!
+ data.reload
+
+ assert_equal 2, data.overloaded_float
+ assert_kind_of Fixnum, OverloadedType.last.overloaded_float
+ assert_equal 2.0, UnoverloadedType.last.overloaded_float
+ assert_kind_of Float, UnoverloadedType.last.overloaded_float
+ end
+
+ def test_properties_assigned_in_constructor
+ data = OverloadedType.new(overloaded_float: '3.3')
+
+ assert_equal 3, data.overloaded_float
+ end
+
+ def test_overloaded_properties_with_limit
+ assert_equal 50, OverloadedType.columns_hash['overloaded_string_with_limit'].limit
+ assert_equal 255, UnoverloadedType.columns_hash['overloaded_string_with_limit'].limit
+ end
+
+ def test_nonexistent_attribute
+ data = OverloadedType.new(non_existent_decimal: 1)
+
+ assert_equal BigDecimal.new(1), data.non_existent_decimal
+ assert_raise ActiveRecord::UnknownAttributeError do
+ UnoverloadedType.new(non_existent_decimal: 1)
+ end
+ end
+
+ def test_changing_defaults
+ data = OverloadedType.new
+ unoverloaded_data = UnoverloadedType.new
+
+ assert_equal 'the overloaded default', data.string_with_default
+ assert_equal 'the original default', unoverloaded_data.string_with_default
+ end
+
+ def test_children_inherit_custom_properties
+ data = ChildOfOverloadedType.new(overloaded_float: '4.4')
+
+ assert_equal 4, data.overloaded_float
+ end
+
+ def test_children_can_override_parents
+ data = GrandchildOfOverloadedType.new(overloaded_float: '4.4')
+
+ assert_equal 4.4, data.overloaded_float
+ end
+
+ def test_overloading_properties_does_not_change_column_order
+ column_names = OverloadedType.column_names
+ assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names
+ end
+
+ def test_caches_are_cleared
+ klass = Class.new(OverloadedType)
+
+ assert_equal 6, klass.columns.length
+ assert_not klass.columns_hash.key?('wibble')
+ assert_equal 6, klass.column_types.length
+ assert_equal 6, klass.column_defaults.length
+ assert_not klass.column_names.include?('wibble')
+ assert_equal 5, klass.content_columns.length
+
+ klass.attribute :wibble, Type::Value.new
+
+ assert_equal 7, klass.columns.length
+ assert klass.columns_hash.key?('wibble')
+ assert_equal 7, klass.column_types.length
+ assert_equal 7, klass.column_defaults.length
+ assert klass.column_names.include?('wibble')
+ assert_equal 6, klass.content_columns.length
+ end
+ end
+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 2e5b8cffa6..8f83cf7cb4 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -102,8 +102,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_columns_should_obey_set_primary_key
- pk = Subscriber.columns.find { |x| x.name == 'nick' }
- assert pk.primary, 'nick should be primary key'
+ pk = Subscriber.columns_hash[Subscriber.primary_key]
+ assert_equal 'nick', pk.name, 'nick should be primary key'
end
def test_primary_key_with_no_id
@@ -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
@@ -531,6 +518,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Topic.find('1-meowmeow'), Topic.find(1)
end
+ def test_find_by_slug_with_array
+ assert_equal Topic.find(['1-meowmeow', '2-hello']), Topic.find([1, 2])
+ end
+
def test_equality_of_new_records
assert_not_equal Topic.new, Topic.new
assert_equal false, Topic.new == Topic.new
@@ -685,8 +676,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 = {
@@ -699,8 +690,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"
@@ -787,8 +778,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
@@ -987,6 +984,10 @@ class BasicsTest < ActiveRecord::TestCase
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
+
+ attribute :world_population, Type::Integer.new
+ attribute :my_house_population, Type::Integer.new
+ attribute :atoms_in_universe, Type::Integer.new
end
def test_big_decimal_conditions
@@ -1487,15 +1488,14 @@ class BasicsTest < ActiveRecord::TestCase
attrs = topic.attributes.dup
attrs.delete 'id'
- typecast = Class.new {
+ typecast = Class.new(ActiveRecord::Type::Value) {
def type_cast value
"t.lo"
end
}
types = { 'author_name' => typecast.new }
- topic = Topic.allocate.init_with 'attributes' => attrs,
- 'column_types' => types
+ topic = Topic.instantiate(attrs, types)
assert_equal 't.lo', topic.author_name
end
@@ -1596,4 +1596,11 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal after_handler, new_handler
assert_equal orig_handler, klass.connection_handler
end
+
+ # Note: This is a performance optimization for Array#uniq and Hash#[] with
+ # AR::Base objects. If the future has made this irrelevant, feel free to
+ # delete this.
+ test "records without an id have unique hashes" do
+ assert_not_equal Post.new.hash, Post.new.hash
+ end
end
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 9a486cf8b8..ccf2be369d 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
@@ -21,7 +21,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
name = binary.name
- # Mysql adapter doesn't properly encode things, so we have to do it
+ # MySQL adapter doesn't properly encode things, so we have to do it
if current_adapter?(:MysqlAdapter)
name.force_encoding(Encoding::UTF_8)
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 40f73cd68c..0bc7ee6d64 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -21,7 +21,7 @@ module ActiveRecord
super
@connection = ActiveRecord::Base.connection
@subscriber = LogListener.new
- @pk = Topic.columns.find { |c| c.primary }
+ @pk = Topic.columns_hash[Topic.primary_key]
@subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
@@ -60,12 +60,10 @@ module ActiveRecord
end
def test_logs_bind_vars
- pk = Topic.columns.find { |x| x.primary }
-
payload = {
:name => 'SQL',
:sql => 'select * from topics where id = ?',
- :binds => [[pk, 10]]
+ :binds => [[@pk, 10]]
}
event = ActiveSupport::Notifications::Event.new(
'foo',
@@ -87,7 +85,7 @@ module ActiveRecord
}.new
logger.sql event
- assert_match([[pk.name, 10]].inspect, logger.debugs.first)
+ assert_match([[@pk.name, 10]].inspect, logger.debugs.first)
end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index b8de78934e..222b1505a8 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -15,6 +15,10 @@ Company.has_many :accounts
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
+
+ attribute :world_population, Type::Integer.new
+ attribute :my_house_population, Type::Integer.new
+ attribute :atoms_in_universe, Type::Integer.new
end
class CalculationsTest < ActiveRecord::TestCase
@@ -606,4 +610,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [1,2,3,4,5], taks_relation.pluck(:id)
assert_equal [false, true, true, true, true], taks_relation.pluck(:approved)
end
+
+ def test_pluck_columns_with_same_name
+ expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]]
+ actual = Topic.joins(:replies)
+ .pluck('topics.title', 'replies_topics.title')
+ assert_equal expected, actual
+ end
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index c1dd1f1c69..bcfd66b4bf 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -11,30 +11,10 @@ module ActiveRecord
@viz = @adapter.schema_creation
end
- def test_can_set_coder
- column = Column.new("title", nil, "varchar(20)")
- column.coder = YAML
- assert_equal YAML, column.coder
- end
-
- def test_encoded?
- column = Column.new("title", nil, "varchar(20)")
- assert !column.encoded?
-
- column.coder = YAML
- assert column.encoded?
- end
-
- def test_type_case_coded_column
- column = Column.new("title", nil, "varchar(20)")
- column.coder = YAML
- assert_equal "hello", column.type_cast("--- hello")
- end
-
# 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(limit: 20))
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -42,7 +22,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(limit: 20))
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -50,7 +30,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(limit: 20), "varchar(20)", false)
column_def = ColumnDefinition.new(
column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
@@ -59,68 +39,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
deleted file mode 100644
index 2a6d8cc2ab..0000000000
--- a/activerecord/test/cases/column_test.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-require "cases/helper"
-require 'models/company'
-
-module ActiveRecord
- module ConnectionAdapters
- class ColumnTest < ActiveRecord::TestCase
- def test_type_cast_boolean
- column = Column.new("field", nil, "boolean")
- assert column.type_cast('').nil?
- assert column.type_cast(nil).nil?
-
- assert column.type_cast(true)
- assert column.type_cast(1)
- assert column.type_cast('1')
- assert column.type_cast('t')
- assert column.type_cast('T')
- assert column.type_cast('true')
- assert column.type_cast('TRUE')
- assert column.type_cast('on')
- assert column.type_cast('ON')
-
- # explicitly check for false vs nil
- assert_equal false, column.type_cast(false)
- assert_equal false, column.type_cast(0)
- assert_equal false, column.type_cast('0')
- assert_equal false, column.type_cast('f')
- assert_equal false, column.type_cast('F')
- assert_equal false, column.type_cast('false')
- assert_equal false, column.type_cast('FALSE')
- assert_equal false, column.type_cast('off')
- assert_equal false, column.type_cast('OFF')
- assert_equal false, column.type_cast(' ')
- assert_equal false, column.type_cast("\u3000\r\n")
- assert_equal false, column.type_cast("\u0000")
- assert_equal false, column.type_cast('SOMETHING RANDOM')
- end
-
- def test_type_cast_integer
- column = Column.new("field", nil, "integer")
- assert_equal 1, column.type_cast(1)
- assert_equal 1, column.type_cast('1')
- assert_equal 1, column.type_cast('1ignore')
- assert_equal 0, column.type_cast('bad1')
- assert_equal 0, column.type_cast('bad')
- assert_equal 1, column.type_cast(1.7)
- assert_equal 0, column.type_cast(false)
- assert_equal 1, column.type_cast(true)
- assert_nil column.type_cast(nil)
- end
-
- def test_type_cast_non_integer_to_integer
- column = Column.new("field", nil, "integer")
- 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")
- 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")
- assert_nil column.type_cast(Object.new)
- end
-
- def test_type_cast_nan_and_infinity_to_integer
- column = Column.new("field", nil, "integer")
- assert_nil column.type_cast(Float::NAN)
- assert_nil column.type_cast(1.0/0.0)
- end
-
- def test_type_cast_time
- column = Column.new("field", nil, "time")
- assert_equal nil, column.type_cast(nil)
- assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast('ABC')
-
- time_string = Time.now.utc.strftime("%T")
- assert_equal time_string, column.type_cast(time_string).strftime("%T")
- 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
- end
-
- def test_type_cast_date
- column = Column.new("field", nil, "date")
- 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')
-
- date_string = Time.now.utc.strftime("%F")
- assert_equal date_string, column.type_cast(date_string).strftime("%F")
- end
-
- def test_type_cast_duration_to_integer
- column = Column.new("field", nil, "integer")
- assert_equal 1800, column.type_cast(30.minutes)
- assert_equal 7200, column.type_cast(2.hours)
- end
-
- 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")
- end
- 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 f2d18e812d..3e33b30144 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -2,199 +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_current_env_symbol_key
- ENV['DATABASE_URL'] = "postgres://localhost/foo"
- config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = resolve(: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("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(: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(: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
- 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("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_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } }
- actual = klass.new(config).resolve
- 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(: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 = klass.new(config).resolve
- 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 = klass.new(config).resolve
- expected = { "default_env" =>
- { "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["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 = klass.new(config).resolve
- 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 = klass.new(config).resolve
- 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 = klass.new(config).resolve
- expected = { "default_env" =>
- { "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..e1b2804a18
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -0,0 +1,204 @@
+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_database_url_with_ipv6_host_and_port
+ ENV['DATABASE_URL'] = "postgres://[::1]:5454/foo"
+
+ config = {}
+ actual = resolve_config(config)
+ expected = { "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "::1",
+ "port" => 5454 }
+ assert_equal expected, actual["default_env"]
+ 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_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
new file mode 100644
index 0000000000..d5c1dc1e5d
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -0,0 +1,101 @@
+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'
+ end
+
+ def test_decimal_without_scale
+ types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)}
+ types.each do |type|
+ cast_type = @connection.type_map.lookup(type)
+
+ assert_equal :decimal, cast_type.type
+ assert_equal 2, cast_type.type_cast_from_user(2.1)
+ end
+ 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/core_test.rb b/activerecord/test/cases/core_test.rb
index 2a52bf574c..715d92af99 100644
--- a/activerecord/test/cases/core_test.rb
+++ b/activerecord/test/cases/core_test.rb
@@ -1,6 +1,8 @@
require 'cases/helper'
require 'models/person'
require 'models/topic'
+require 'pp'
+require 'active_support/core_ext/string/strip'
class NonExistentTable < ActiveRecord::Base; end
@@ -30,4 +32,70 @@ class CoreTest < ActiveRecord::TestCase
def test_inspect_class_without_table
assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
end
+
+ def test_pretty_print_new
+ topic = Topic.new
+ actual = ''
+ PP.pp(topic, StringIO.new(actual))
+ expected = <<-PRETTY.strip_heredoc
+ #<Topic:0xXXXXXX
+ id: nil,
+ title: nil,
+ author_name: nil,
+ author_email_address: "test@test.com",
+ written_on: nil,
+ bonus_time: nil,
+ last_read: nil,
+ content: nil,
+ important: nil,
+ approved: true,
+ replies_count: 0,
+ unique_replies_count: 0,
+ parent_id: nil,
+ parent_title: nil,
+ type: nil,
+ group: nil,
+ created_at: nil,
+ updated_at: nil>
+ PRETTY
+ assert actual.start_with?(expected.split('XXXXXX').first)
+ assert actual.end_with?(expected.split('XXXXXX').last)
+ end
+
+ def test_pretty_print_persisted
+ topic = topics(:first)
+ actual = ''
+ PP.pp(topic, StringIO.new(actual))
+ expected = <<-PRETTY.strip_heredoc
+ #<Topic:0x\\w+
+ id: 1,
+ title: "The First Topic",
+ author_name: "David",
+ author_email_address: "david@loudthinking.com",
+ written_on: 2003-07-16 14:28:11 UTC,
+ bonus_time: 2000-01-01 14:28:00 UTC,
+ last_read: Thu, 15 Apr 2004,
+ content: "Have a nice day",
+ important: nil,
+ approved: false,
+ replies_count: 1,
+ unique_replies_count: 0,
+ parent_id: nil,
+ parent_title: nil,
+ type: nil,
+ group: nil,
+ created_at: [^,]+,
+ updated_at: [^,>]+>
+ PRETTY
+ assert_match(/\A#{expected}\z/, actual)
+ end
+
+ def test_pretty_print_uninitialized
+ topic = Topic.allocate
+ actual = ''
+ PP.pp(topic, StringIO.new(actual))
+ expected = "#<Topic:XXXXXX not initialized>\n"
+ assert actual.start_with?(expected.split('XXXXXX').first)
+ assert actual.end_with?(expected.split('XXXXXX').last)
+ 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..92144bc802 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
@@ -206,6 +206,11 @@ if current_adapter?(:PostgreSQLAdapter)
assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db"
end
+ def test_default_containing_quote_and_colons
+ @connection.execute "ALTER TABLE defaults ALTER COLUMN string_col SET DEFAULT 'foo''::bar'"
+ assert_equal "foo'::bar", Default.new.string_col
+ end
+
teardown do
@connection.schema_search_path = @old_search_path
Default.reset_column_information
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index df4183c065..5d6601a881 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -445,11 +445,20 @@ class DirtyTest < ActiveRecord::TestCase
def test_save_should_store_serialized_attributes_even_with_partial_writes
with_partial_writes(Topic) do
topic = Topic.create!(:content => {:a => "a"})
+
+ assert_not topic.changed?
+
topic.content[:b] = "b"
- #assert topic.changed? # Known bug, will fail
+
+ assert topic.changed?
+
topic.save!
+
+ assert_not topic.changed?
assert_equal "b", topic.content[:b]
+
topic.reload
+
assert_equal "b", topic.content[:b]
end
end
@@ -616,6 +625,32 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ test "defaults with type that implements `type_cast_for_database`" do
+ type = Class.new(ActiveRecord::Type::Value) do
+ def type_cast(value)
+ value.to_i
+ end
+
+ def type_cast_for_database(value)
+ value.to_s
+ end
+ end
+
+ model_class = Class.new(ActiveRecord::Base) do
+ self.table_name = 'numeric_data'
+ attribute :foo, type.new, default: 1
+ end
+
+ model = model_class.new
+ assert_not model.foo_changed?
+
+ model = model_class.new(foo: 1)
+ assert_not model.foo_changed?
+
+ model = model_class.new(foo: '1')
+ assert_not model.foo_changed?
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
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/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/finder_test.rb b/activerecord/test/cases/finder_test.rb
index c0440744e9..bd77c412f6 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -33,6 +33,17 @@ class FinderTest < ActiveRecord::TestCase
assert_equal(topics(:first).title, Topic.find(1).title)
end
+ def test_find_with_proc_parameter_and_block
+ exception = assert_raises(RuntimeError) do
+ Topic.all.find(-> { raise "should happen" }) { |e| e.title == "non-existing-title" }
+ end
+ assert_equal "should happen", exception.message
+
+ assert_nothing_raised(RuntimeError) do
+ Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title }
+ end
+ end
+
def test_find_passing_active_record_object_is_deprecated
assert_deprecated do
Topic.find(Topic.last)
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 8bbc0af758..042fdaf0bb 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -84,12 +84,6 @@ class FixturesTest < ActiveRecord::TestCase
assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}"
end
- def test_create_symbol_fixtures_is_deprecated
- assert_deprecated do
- ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => 'Course') { Course.connection }
- end
- end
-
def test_attributes
topics = create_fixtures("topics").first
assert_equal("The First Topic", topics["first"]["title"])
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index eaf2cada9d..937646b09a 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -9,6 +9,7 @@ require 'active_record'
require 'cases/test_case'
require 'active_support/dependencies'
require 'active_support/logger'
+require 'active_support/core_ext/string/strip'
require 'support/config'
require 'support/connection'
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..c221430757 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -272,10 +272,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert p.treasures.empty?
assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
end
-
- def test_quoted_locking_column_is_deprecated
- assert_deprecated { ActiveRecord::Base.quoted_locking_column }
- end
end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
@@ -339,8 +335,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 +361,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..93adbdd05b 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/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index 84224e6e4c..319d3e1af3 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
module ActiveRecord
class Migration
class LoggerTest < ActiveRecord::TestCase
- # mysql can't roll back ddl changes
+ # MySQL can't roll back ddl changes
self.use_transactional_fixtures = false
Migration = Struct.new(:name, :version) do
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index 2a7fafc559..a52b58c4ac 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
@@ -82,7 +74,7 @@ module ActiveRecord
pk, seq = connection.pk_and_sequence_for('octopi')
- assert_equal "octopi_#{pk}_seq", seq
+ assert_equal ConnectionAdapters::PostgreSQL::Name.new("public", "octopi_#{pk}_seq"), seq
end
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 455ec78f68..9855835e27 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -11,7 +11,13 @@ require MIGRATIONS_ROOT + "/rename/1_we_need_things"
require MIGRATIONS_ROOT + "/rename/2_rename_things"
require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers"
-class BigNumber < ActiveRecord::Base; end
+class BigNumber < ActiveRecord::Base
+ unless current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
+ attribute :value_of_e, Type::Integer.new
+ end
+ attribute :world_population, Type::Integer.new
+ attribute :my_house_population, Type::Integer.new
+end
class Reminder < ActiveRecord::Base; end
@@ -327,47 +333,6 @@ class MigrationTest < ActiveRecord::TestCase
Reminder.reset_table_name
end
- def test_proper_table_name_on_migrator
- reminder_class = new_isolated_reminder_class
- assert_deprecated do
- assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
- end
- assert_deprecated do
- assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
- end
- assert_deprecated do
- assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(reminder_class)
- end
- reminder_class.reset_table_name
- assert_deprecated do
- assert_equal reminder_class.table_name, ActiveRecord::Migrator.proper_table_name(reminder_class)
- end
-
- # Use the model's own prefix/suffix if a model is given
- ActiveRecord::Base.table_name_prefix = "ARprefix_"
- ActiveRecord::Base.table_name_suffix = "_ARsuffix"
- reminder_class.table_name_prefix = 'prefix_'
- reminder_class.table_name_suffix = '_suffix'
- reminder_class.reset_table_name
- assert_deprecated do
- assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(reminder_class)
- end
- reminder_class.table_name_prefix = ''
- reminder_class.table_name_suffix = ''
- reminder_class.reset_table_name
-
- # Use AR::Base's prefix/suffix if string or symbol is given
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
- reminder_class.reset_table_name
- assert_deprecated do
- assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
- end
- assert_deprecated do
- assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
- end
- end
-
def test_proper_table_name_on_migration
reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
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 9209672ac5..28341d0b42 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'
@@ -234,19 +235,12 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_save_with_duping_of_destroyed_object
- developer = Developer.create(name: "Kuldeep")
+ developer = Developer.first
developer.destroy
new_developer = developer.dup
new_developer.save
assert new_developer.persisted?
- end
-
- def test_dup_of_destroyed_object_is_not_destroyed
- developer = Developer.create(name: "Kuldeep")
- developer.destroy
- new_developer = developer.dup
- new_developer.save
- assert_equal new_developer.destroyed?, false
+ assert_not new_developer.destroyed?
end
def test_create_many
@@ -256,11 +250,9 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_create_columns_not_equal_attributes
- topic = Topic.allocate.init_with(
- 'attributes' => {
- 'title' => 'Another New Topic',
- 'does_not_exist' => 'test'
- }
+ topic = Topic.instantiate(
+ 'title' => 'Another New Topic',
+ 'does_not_exist' => 'test'
)
assert_nothing_raised { topic.save }
end
@@ -306,10 +298,7 @@ class PersistenceTest < ActiveRecord::TestCase
topic.title = "Still another topic"
topic.save
- topic_reloaded = Topic.allocate
- topic_reloaded.init_with(
- 'attributes' => topic.attributes.merge('does_not_exist' => 'test')
- )
+ topic_reloaded = Topic.instantiate(topic.attributes.merge('does_not_exist' => 'test'))
topic_reloaded.title = 'A New Topic'
assert_nothing_raised { topic_reloaded.save }
end
@@ -339,6 +328,15 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal "Reply", topic.type
end
+ def test_update_sti_subclass_type
+ assert_instance_of Topic, topics(:first)
+
+ reply = topics(:first).becomes!(Reply)
+ assert_instance_of Reply, reply
+ reply.save!
+ assert_instance_of Reply, Reply.find(reply.id)
+ end
+
def test_update_after_create
klass = Class.new(Topic) do
def self.name; 'Topic'; end
@@ -511,14 +509,14 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_column_should_not_leave_the_object_dirty
topic = Topic.find(1)
- topic.update_column("content", "Have a nice day")
+ topic.update_column("content", "--- Have a nice day\n...\n")
topic.reload
- topic.update_column(:content, "You too")
+ topic.update_column(:content, "--- You too\n...\n")
assert_equal [], topic.changed
topic.reload
- topic.update_column("content", "Have a nice day")
+ topic.update_column("content", "--- Have a nice day\n...\n")
assert_equal [], topic.changed
end
@@ -602,14 +600,14 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_columns_should_not_leave_the_object_dirty
topic = Topic.find(1)
- topic.update({ "content" => "Have a nice day", :author_name => "Jose" })
+ topic.update({ "content" => "--- Have a nice day\n...\n", :author_name => "Jose" })
topic.reload
- topic.update_columns({ content: "You too", "author_name" => "Sebastian" })
+ topic.update_columns({ content: "--- You too\n...\n", "author_name" => "Sebastian" })
assert_equal [], topic.changed
topic.reload
- topic.update_columns({ content: "Have a nice day", author_name: "Jose" })
+ topic.update_columns({ content: "--- Have a nice day\n...\n", author_name: "Jose" })
assert_equal [], topic.changed
end
@@ -836,4 +834,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..f43483d291 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -92,6 +92,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
def test_primary_key_prefix
+ old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
ActiveRecord::Base.primary_key_prefix_type = :table_name
Topic.reset_primary_key
assert_equal "topicid", Topic.primary_key
@@ -103,6 +104,8 @@ class PrimaryKeysTest < ActiveRecord::TestCase
ActiveRecord::Base.primary_key_prefix_type = nil
Topic.reset_primary_key
assert_equal "id", Topic.primary_key
+ ensure
+ ActiveRecord::Base.primary_key_prefix_type = old_primary_key_prefix_type
end
def test_delete_should_quote_pkey
@@ -149,38 +152,6 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key
end
- def test_two_models_with_same_table_but_different_primary_key
- k1 = Class.new(ActiveRecord::Base)
- k1.table_name = 'posts'
- k1.primary_key = 'id'
-
- k2 = Class.new(ActiveRecord::Base)
- k2.table_name = 'posts'
- k2.primary_key = 'title'
-
- assert k1.columns.find { |c| c.name == 'id' }.primary
- assert !k1.columns.find { |c| c.name == 'title' }.primary
- assert k1.columns_hash['id'].primary
- assert !k1.columns_hash['title'].primary
-
- assert !k2.columns.find { |c| c.name == 'id' }.primary
- assert k2.columns.find { |c| c.name == 'title' }.primary
- assert !k2.columns_hash['id'].primary
- assert k2.columns_hash['title'].primary
- end
-
- def test_models_with_same_table_have_different_columns
- k1 = Class.new(ActiveRecord::Base)
- k1.table_name = 'posts'
-
- k2 = Class.new(ActiveRecord::Base)
- k2.table_name = 'posts'
-
- k1.columns.zip(k2.columns).each do |col1, col2|
- assert !col1.equal?(col2)
- end
- end
-
def test_auto_detect_primary_key_from_schema
MixedCaseMonkey.reset_primary_key
assert_equal "monkeyID", MixedCaseMonkey.primary_key
@@ -219,3 +190,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/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index e2439b9a24..bbd5298da1 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -3,14 +3,6 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class QuotingTest < ActiveRecord::TestCase
- class FakeColumn < ActiveRecord::ConnectionAdapters::Column
- attr_accessor :type
-
- def initialize type
- @type = type
- end
- end
-
def setup
@quoter = Class.new { include Quoting }.new
end
@@ -101,12 +93,12 @@ module ActiveRecord
def test_quote_true
assert_equal @quoter.quoted_true, @quoter.quote(true, nil)
- assert_equal '1', @quoter.quote(true, Struct.new(:type).new(:integer))
+ assert_equal '1', @quoter.quote(true, Type::Integer.new)
end
def test_quote_false
assert_equal @quoter.quoted_false, @quoter.quote(false, nil)
- assert_equal '0', @quoter.quote(false, Struct.new(:type).new(:integer))
+ assert_equal '0', @quoter.quote(false, Type::Integer.new)
end
def test_quote_float
@@ -166,26 +158,26 @@ module ActiveRecord
end
def test_quote_string_int_column
- assert_equal "1", @quoter.quote('1', FakeColumn.new(:integer))
- assert_equal "1", @quoter.quote('1.2', FakeColumn.new(:integer))
+ assert_equal "1", @quoter.quote('1', Type::Integer.new)
+ assert_equal "1", @quoter.quote('1.2', Type::Integer.new)
end
def test_quote_string_float_column
- assert_equal "1.0", @quoter.quote('1', FakeColumn.new(:float))
- assert_equal "1.2", @quoter.quote('1.2', FakeColumn.new(:float))
+ assert_equal "1.0", @quoter.quote('1', Type::Float.new)
+ assert_equal "1.2", @quoter.quote('1.2', Type::Float.new)
end
def test_quote_as_mb_chars_binary_column
string = ActiveSupport::Multibyte::Chars.new('lo\l')
- assert_equal "'lo\\\\l'", @quoter.quote(string, FakeColumn.new(:binary))
+ assert_equal "'lo\\\\l'", @quoter.quote(string, Type::Binary.new)
end
def test_quote_binary_without_string_to_binary
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:binary))
+ assert_equal "'lo\\\\l'", @quoter.quote('lo\l', Type::Binary.new)
end
def test_string_with_crazy_column
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo))
+ assert_equal "'lo\\\\l'", @quoter.quote('lo\l')
end
def test_quote_duration
@@ -193,7 +185,7 @@ module ActiveRecord
end
def test_quote_duration_int_column
- assert_equal "7200", @quoter.quote(2.hours, FakeColumn.new(:integer))
+ assert_equal "7200", @quoter.quote(2.hours, Type::Integer.new)
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index c085fcf161..c6e73f78cc 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -80,6 +80,26 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal :integer, @first.column_for_attribute("id").type
end
+ def test_non_existent_columns_return_null_object
+ column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
+ assert_equal "attribute_that_doesnt_exist", column.name
+ assert_equal nil, column.sql_type
+ assert_equal nil, column.type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ end
+
+ def test_non_existent_columns_are_identity_types
+ column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ object = Object.new
+
+ assert_equal object, column.type_cast_from_database(object)
+ assert_equal object, column.type_cast_from_user(object)
+ assert_equal object, column.type_cast_for_database(object)
+ end
+
def test_reflection_klass_for_nested_class_name
reflection = MacroReflection.new(:company, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base)
assert_nothing_raised do
@@ -200,7 +220,12 @@ 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_not_equal Object.new, Firm._reflections['clients']
+ end
+
+ def test_has_and_belongs_to_many_reflection
+ assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro
+ assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name
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 c81a3002d6..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
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index c628ca44ff..b9e69bdb08 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/relations_test.rb b/activerecord/test/cases/relations_test.rb
index d054dfa25a..88df997a2f 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -366,6 +366,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)
end
+ def test_null_relation_sum
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:id).sum(:id)
+ assert_equal 0, ac.engines.count
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:id).sum(:id)
+ assert_equal 0, ac.engines.count
+ end
def test_null_relation_count
ac = Aircraft.new
@@ -376,6 +384,42 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 0, ac.engines.count
end
+ def test_null_relation_size
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:id).size
+ assert_equal 0, ac.engines.size
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:id).size
+ assert_equal 0, ac.engines.size
+ end
+
+ def test_null_relation_average
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:car_id).average(:id)
+ assert_equal nil, ac.engines.average(:id)
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:car_id).average(:id)
+ assert_equal nil, ac.engines.average(:id)
+ end
+
+ def test_null_relation_minimum
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id)
+ assert_equal nil, ac.engines.minimum(:id)
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id)
+ assert_equal nil, ac.engines.minimum(:id)
+ end
+
+ def test_null_relation_maximum
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id)
+ assert_equal nil, ac.engines.maximum(:id)
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id)
+ assert_equal nil, ac.engines.maximum(:id)
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -699,6 +743,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [], relation.to_a
end
+ def test_typecasting_where_with_array
+ ids = Author.pluck(:id)
+ slugs = ids.map { |id| "#{id}-as-a-slug" }
+
+ assert_equal Author.all.to_a, Author.where(id: slugs).to_a
+ end
+
def test_find_all_using_where_with_relation
david = authors(:david)
# switching the lines below would succeed in current rails
@@ -831,8 +882,12 @@ 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
@@ -896,6 +951,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
@@ -1643,10 +1706,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
@@ -1656,8 +1717,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)
@@ -1665,19 +1725,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/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index fd0ef2f89f..5f02d39e32 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -1,10 +1,9 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
class SchemaDumperTest < ActiveRecord::TestCase
- def setup
- super
+ setup do
ActiveRecord::SchemaMigration.create_table
- @stream = StringIO.new
end
def standard_dump
@@ -25,7 +24,8 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_magic_comment
- assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump
+ output = standard_dump
+ assert_match "# encoding: #{@stream.external_encoding.name}", output
end
def test_schema_dump
@@ -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|uuid)\s+"/)
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid|point)\s+"/)
match[0].length
end
end
@@ -353,9 +353,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = standard_dump
# Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
if current_adapter?(:OracleAdapter)
- assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38,\s+scale: 0}, output
+ assert_match %r{t.integer\s+"atoms_in_universe",\s+precision: 38}, output
else
- assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55,\s+scale: 0}, output
+ assert_match %r{t.decimal\s+"atoms_in_universe",\s+precision: 55}, output
end
end
@@ -394,7 +394,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = standard_dump
assert_no_match %r{create_table "foo_.+_bar"}, output
- assert_no_match %r{create_index "foo_.+_bar"}, output
+ assert_no_match %r{add_index "foo_.+_bar"}, output
assert_no_match %r{create_table "schema_migrations"}, output
ensure
migration.migrate(:down)
@@ -404,3 +404,31 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+
+class SchemaDumperDefaultsTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :defaults, force: true do |t|
+ t.string :string_with_default, default: "Hello!"
+ t.date :date_with_default, default: '2014-06-05'
+ t.datetime :datetime_with_default, default: "2014-06-05 07:17:04"
+ t.time :time_with_default, default: "07:17:04"
+ end
+ end
+
+ teardown do
+ return unless @connection
+ @connection.execute 'DROP TABLE IF EXISTS defaults'
+ end
+
+ def test_schema_dump_defaults_with_universally_supported_types
+ output = dump_table_schema('defaults')
+
+ assert_match %r{t\.string\s+"string_with_default",\s+default: "Hello!"}, output
+ assert_match %r{t\.date\s+"date_with_default",\s+default: '2014-06-05'}, output
+ assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: '2014-06-05 07:17:04'}, output
+ assert_match %r{t\.time\s+"time_with_default",\s+default: '2000-01-01 07:17:04'}, output
+ end
+end
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index c46060a646..3f52e80e11 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -1,8 +1,11 @@
require "cases/helper"
require 'models/contact'
require 'models/topic'
+require 'models/book'
class SerializationTest < ActiveRecord::TestCase
+ fixtures :books
+
FORMATS = [ :xml, :json ]
def setup
@@ -65,4 +68,28 @@ class SerializationTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.include_root_in_json = original_root_in_json
end
+
+ def test_read_attribute_for_serialization_with_format_without_method_missing
+ klazz = Class.new(ActiveRecord::Base)
+ klazz.table_name = 'books'
+
+ book = klazz.new
+ assert_nil book.read_attribute_for_serialization(:format)
+ end
+
+ def test_read_attribute_for_serialization_with_format_after_init
+ klazz = Class.new(ActiveRecord::Base)
+ klazz.table_name = 'books'
+
+ book = klazz.new(format: 'paperback')
+ assert_equal 'paperback', book.read_attribute_for_serialization(:format)
+ end
+
+ def test_read_attribute_for_serialization_with_format_after_find
+ klazz = Class.new(ActiveRecord::Base)
+ klazz.table_name = 'books'
+
+ book = klazz.find(books(:awdr).id)
+ assert_equal 'paperback', book.read_attribute_for_serialization(:format)
+ end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 5609cf310c..186a1a2ade 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -14,8 +14,17 @@ class SerializedAttributeTest < ActiveRecord::TestCase
Topic.serialize("content")
end
+ def test_serialize_does_not_eagerly_load_columns
+ assert_no_queries do
+ Topic.reset_column_information
+ Topic.serialize(:content)
+ end
+ end
+
def test_list_of_serialized_attributes
- assert_equal %w(content), Topic.serialized_attributes.keys
+ assert_deprecated do
+ assert_equal %w(content), Topic.serialized_attributes.keys
+ end
end
def test_serialized_attribute
@@ -29,12 +38,6 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal(myobj, topic.content)
end
- def test_serialized_attribute_init_with
- topic = Topic.allocate
- topic.init_with('attributes' => { 'content' => '--- foo' })
- assert_equal 'foo', topic.content
- end
-
def test_serialized_attribute_in_base_class
Topic.serialize("content", Hash)
@@ -46,34 +49,22 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal(hash, important_topic.content)
end
- # This test was added to fix GH #4004. Obviously the value returned
- # is not really the value 'before type cast' so we should maybe think
- # about changing that in the future.
- def test_serialized_attribute_before_type_cast_returns_unserialized_value
- Topic.serialize :content, Hash
-
- t = Topic.new(content: { foo: :bar })
- assert_equal({ foo: :bar }, t.content_before_type_cast)
- t.save!
- t.reload
- assert_equal({ foo: :bar }, t.content_before_type_cast)
- end
-
- def test_serialized_attributes_before_type_cast_returns_unserialized_value
+ def test_serialized_attributes_from_database_on_subclass
Topic.serialize :content, Hash
- t = Topic.new(content: { foo: :bar })
- assert_equal({ foo: :bar }, t.attributes_before_type_cast["content"])
+ t = Reply.new(content: { foo: :bar })
+ assert_equal({ foo: :bar }, t.content)
t.save!
- t.reload
- assert_equal({ foo: :bar }, t.attributes_before_type_cast["content"])
+ t = Reply.last
+ assert_equal({ foo: :bar }, t.content)
end
def test_serialized_attribute_calling_dup_method
Topic.serialize :content, JSON
- t = Topic.new(:content => { :foo => :bar }).dup
- assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ orig = Topic.new(content: { foo: :bar })
+ clone = orig.dup
+ assert_equal(orig.content, clone.content)
end
def test_serialized_attribute_declared_in_subclass
@@ -116,8 +107,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase
def test_serialized_attribute_should_raise_exception_on_save_with_wrong_type
Topic.serialize(:content, Hash)
- topic = Topic.new(:content => "string")
- assert_raise(ActiveRecord::SerializationTypeMismatch) { topic.save }
+ assert_raise(ActiveRecord::SerializationTypeMismatch) do
+ topic = Topic.new(content: 'string')
+ topic.save
+ end
end
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
@@ -168,45 +161,22 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
def test_serialize_with_coder
- coder = Class.new {
- # Identity
- def load(thing)
- thing
- end
-
- # base 64
- def dump(thing)
- [thing].pack('m')
+ some_class = Struct.new(:foo) do
+ def self.dump(value)
+ value.foo
end
- }.new
- Topic.serialize(:content, coder)
- s = 'hello world'
- topic = Topic.new(:content => s)
- assert topic.save
- topic = topic.reload
- assert_equal [s].pack('m'), topic.content
- end
-
- def test_serialize_with_bcrypt_coder
- crypt_coder = Class.new {
- def load(thing)
- return unless thing
- BCrypt::Password.new thing
+ def self.load(value)
+ new(value)
end
+ end
- def dump(thing)
- BCrypt::Password.create(thing).to_s
- end
- }.new
-
- Topic.serialize(:content, crypt_coder)
- password = 'password'
- topic = Topic.new(:content => password)
- assert topic.save
- topic = topic.reload
- assert_kind_of BCrypt::Password, topic.content
- assert_equal(true, topic.content == password, 'password should equal')
+ Topic.serialize(:content, some_class)
+ topic = Topic.new(:content => some_class.new('my value'))
+ topic.save!
+ topic.reload
+ assert_kind_of some_class, topic.content
+ assert_equal topic.content, some_class.new('my value')
end
def test_serialize_attribute_via_select_method_when_time_zone_available
@@ -235,13 +205,19 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal [], light.long_state
end
- def test_serialized_column_should_not_be_wrapped_twice
- Topic.serialize(:content, MyObject)
+ def test_serialized_column_should_unserialize_after_update_column
+ t = Topic.create(content: "first")
+ assert_equal("first", t.content)
- myobj = MyObject.new('value1', 'value2')
- Topic.create(content: myobj)
- Topic.create(content: myobj)
- type = Topic.column_types["content"]
- assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type)
+ t.update_column(:content, Topic.type_for_attribute('content').type_cast_for_database("second"))
+ assert_equal("second", t.content)
+ end
+
+ def test_serialized_column_should_unserialize_after_update_attribute
+ t = Topic.create(content: "first")
+ assert_equal("first", t.content)
+
+ t.update_attribute(:content, "second")
+ assert_equal("second", t.content)
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..e9cdf94c99 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -163,22 +163,24 @@ class StoreTest < ActiveRecord::TestCase
assert_equal [:width, :height], second_model.stored_attributes[:data]
end
- test "YAML coder initializes the store when a Nil value is given" do
- assert_equal({}, @john.params)
- 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
- test "attributes_for_coder should return stored fields already serialized" do
- attributes = {
- "id" => @john.id,
- "name"=> @john.name,
- "settings" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ncolor: black\n",
- "preferences" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nremember_login: true\n",
- "json_data" => "{\"height\":\"tall\"}", "json_data_empty"=>"{\"is_a_good_guy\":true}",
- "params" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}\n",
- "account_id"=> @john.account_id
- }
+ 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
- assert_equal attributes, @john.attributes_for_coder
+ test "YAML coder initializes the store when a Nil value is given" do
+ assert_equal({}, @john.params)
end
test "dump, load and dump again a model" do
@@ -187,7 +189,6 @@ class StoreTest < ActiveRecord::TestCase
assert_equal @john, loaded
second_dump = YAML.dump(loaded)
- assert_equal dumped, second_dump
assert_equal @john, YAML.load(second_dump)
end
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..0472246f71 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -71,24 +71,6 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal @previously_updated_at, @developer.updated_at
end
- def test_saving_when_callback_sets_record_timestamps_to_false_doesnt_update_its_timestamp
- klass = Class.new(Developer) do
- before_update :cancel_record_timestamps
- def cancel_record_timestamps
- self.record_timestamps = false
- return true
- end
- end
-
- developer = klass.first
- previously_updated_at = developer.updated_at
-
- developer.name = "New Name"
- developer.save!
-
- assert_equal previously_updated_at, developer.updated_at
- end
-
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
@developer.touch(:created_at)
@@ -112,7 +94,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 +152,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
@@ -380,6 +381,19 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal time, pet.updated_at
end
+ def test_timestamp_column_values_are_present_in_the_callbacks
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'people'
+
+ before_create do
+ self.born_at = self.created_at
+ end
+ end
+
+ person = klass.create first_name: 'David'
+ assert_not_equal person.born_at, nil
+ end
+
def test_timestamp_attributes_for_create
toy = Toy.first
assert_equal [:created_at, :created_on], toy.send(:timestamp_attributes_for_create)
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/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb
new file mode 100644
index 0000000000..4e32f92dd0
--- /dev/null
+++ b/activerecord/test/cases/type/type_map_test.rb
@@ -0,0 +1,130 @@
+require "cases/helper"
+
+module ActiveRecord
+ 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_additional_lookup_args
+ mapping = TypeMap.new
+
+ mapping.register_type(/varchar/i) do |type, limit|
+ if limit > 255
+ 'text'
+ else
+ 'string'
+ end
+ end
+ mapping.alias_type(/string/i, 'varchar')
+
+ assert_equal mapping.lookup('varchar', 200), 'string'
+ assert_equal mapping.lookup('varchar', 400), 'text'
+ assert_equal mapping.lookup('string', 400), 'text'
+ 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
+
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
new file mode 100644
index 0000000000..731f8cfba3
--- /dev/null
+++ b/activerecord/test/cases/types_test.rb
@@ -0,0 +1,159 @@
+require "cases/helper"
+require 'models/company'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class TypesTest < ActiveRecord::TestCase
+ def test_type_cast_boolean
+ type = Type::Boolean.new
+ assert type.type_cast_from_user('').nil?
+ assert type.type_cast_from_user(nil).nil?
+
+ assert type.type_cast_from_user(true)
+ assert type.type_cast_from_user(1)
+ assert type.type_cast_from_user('1')
+ assert type.type_cast_from_user('t')
+ assert type.type_cast_from_user('T')
+ assert type.type_cast_from_user('true')
+ assert type.type_cast_from_user('TRUE')
+ assert type.type_cast_from_user('on')
+ assert type.type_cast_from_user('ON')
+
+ # explicitly check for false vs nil
+ assert_equal false, type.type_cast_from_user(false)
+ assert_equal false, type.type_cast_from_user(0)
+ assert_equal false, type.type_cast_from_user('0')
+ assert_equal false, type.type_cast_from_user('f')
+ assert_equal false, type.type_cast_from_user('F')
+ assert_equal false, type.type_cast_from_user('false')
+ assert_equal false, type.type_cast_from_user('FALSE')
+ assert_equal false, type.type_cast_from_user('off')
+ assert_equal false, type.type_cast_from_user('OFF')
+ assert_equal false, type.type_cast_from_user(' ')
+ assert_equal false, type.type_cast_from_user("\u3000\r\n")
+ assert_equal false, type.type_cast_from_user("\u0000")
+ assert_equal false, type.type_cast_from_user('SOMETHING RANDOM')
+ end
+
+ def test_type_cast_string
+ type = Type::String.new
+ assert_equal "1", type.type_cast_from_user(true)
+ assert_equal "0", type.type_cast_from_user(false)
+ assert_equal "123", type.type_cast_from_user(123)
+ end
+
+ def test_type_cast_integer
+ type = Type::Integer.new
+ assert_equal 1, type.type_cast_from_user(1)
+ assert_equal 1, type.type_cast_from_user('1')
+ assert_equal 1, type.type_cast_from_user('1ignore')
+ assert_equal 0, type.type_cast_from_user('bad1')
+ assert_equal 0, type.type_cast_from_user('bad')
+ assert_equal 1, type.type_cast_from_user(1.7)
+ assert_equal 0, type.type_cast_from_user(false)
+ assert_equal 1, type.type_cast_from_user(true)
+ assert_nil type.type_cast_from_user(nil)
+ end
+
+ def test_type_cast_non_integer_to_integer
+ type = Type::Integer.new
+ assert_nil type.type_cast_from_user([1,2])
+ assert_nil type.type_cast_from_user({1 => 2})
+ assert_nil type.type_cast_from_user((1..2))
+ end
+
+ def test_type_cast_activerecord_to_integer
+ type = Type::Integer.new
+ firm = Firm.create(:name => 'Apple')
+ assert_nil type.type_cast_from_user(firm)
+ end
+
+ def test_type_cast_object_without_to_i_to_integer
+ type = Type::Integer.new
+ assert_nil type.type_cast_from_user(Object.new)
+ end
+
+ def test_type_cast_nan_and_infinity_to_integer
+ type = Type::Integer.new
+ assert_nil type.type_cast_from_user(Float::NAN)
+ assert_nil type.type_cast_from_user(1.0/0.0)
+ end
+
+ def test_type_cast_float
+ type = Type::Float.new
+ assert_equal 1.0, type.type_cast_from_user("1")
+ end
+
+ def test_type_cast_decimal
+ type = Type::Decimal.new
+ assert_equal BigDecimal.new("0"), type.type_cast_from_user(BigDecimal.new("0"))
+ assert_equal BigDecimal.new("123"), type.type_cast_from_user(123.0)
+ assert_equal BigDecimal.new("1"), type.type_cast_from_user(:"1")
+ end
+
+ def test_type_cast_binary
+ type = Type::Binary.new
+ assert_equal nil, type.type_cast_from_user(nil)
+ assert_equal "1", type.type_cast_from_user("1")
+ assert_equal 1, type.type_cast_from_user(1)
+ end
+
+ def test_type_cast_time
+ type = Type::Time.new
+ assert_equal nil, type.type_cast_from_user(nil)
+ assert_equal nil, type.type_cast_from_user('')
+ assert_equal nil, type.type_cast_from_user('ABC')
+
+ time_string = Time.now.utc.strftime("%T")
+ assert_equal time_string, type.type_cast_from_user(time_string).strftime("%T")
+ end
+
+ def test_type_cast_datetime_and_timestamp
+ type = Type::DateTime.new
+ assert_equal nil, type.type_cast_from_user(nil)
+ assert_equal nil, type.type_cast_from_user('')
+ assert_equal nil, type.type_cast_from_user(' ')
+ assert_equal nil, type.type_cast_from_user('ABC')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, type.type_cast_from_user(datetime_string).strftime("%FT%T")
+ end
+
+ def test_type_cast_date
+ type = Type::Date.new
+ assert_equal nil, type.type_cast_from_user(nil)
+ assert_equal nil, type.type_cast_from_user('')
+ assert_equal nil, type.type_cast_from_user(' ')
+ assert_equal nil, type.type_cast_from_user('ABC')
+
+ date_string = Time.now.utc.strftime("%F")
+ assert_equal date_string, type.type_cast_from_user(date_string).strftime("%F")
+ end
+
+ def test_type_cast_duration_to_integer
+ type = Type::Integer.new
+ assert_equal 1800, type.type_cast_from_user(30.minutes)
+ assert_equal 7200, type.type_cast_from_user(2.hours)
+ end
+
+ def test_string_to_time_with_timezone
+ [:utc, :local].each do |zone|
+ with_timezone_config default: zone do
+ type = Type::DateTime.new
+ assert_equal Time.utc(2013, 9, 4, 0, 0, 0), type.type_cast_from_user("Wed, 04 Sep 2013 03:00:00 EAT")
+ end
+ end
+ end
+
+ if current_adapter?(:SQLite3Adapter)
+ def test_binary_encoding
+ type = SQLite3Binary.new
+ utf8_string = "a string".encode(Encoding::UTF_8)
+ type_cast = type.type_cast_from_user(utf8_string)
+
+ assert_equal Encoding::ASCII_8BIT, type_cast.encoding
+ end
+ end
+ end
+ end
+end
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..c34e7d5a30 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)
@@ -422,8 +416,9 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
def test_should_support_aliased_attributes
xml = Author.select("name as firstname").to_xml
- array = Hash.from_xml(xml)['authors']
- assert_equal array.size, array.select { |author| author.has_key? 'firstname' }.size
+ Author.all.each do |author|
+ assert xml.include?(%(<firstname>#{author.name}</firstname>)), xml
+ end
end
def test_array_to_xml_including_has_many_association
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 15815d56e4..9f1d110ddb 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -1,8 +1,10 @@
require 'cases/helper'
require 'models/topic'
+require 'models/post'
+require 'models/author'
class YamlSerializationTest < ActiveRecord::TestCase
- fixtures :topics
+ fixtures :topics, :authors, :posts
def test_to_yaml_with_time_with_zone_should_not_raise_exception
with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do
@@ -23,13 +25,6 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content)
end
- def test_encode_with_coder
- topic = Topic.first
- coder = {}
- topic.encode_with coder
- assert_equal({'attributes' => topic.attributes}, coder)
- end
-
def test_psych_roundtrip
topic = Topic.first
assert topic
@@ -47,4 +42,44 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_active_record_relation_serialization
[Topic.all].to_yaml
end
+
+ def test_raw_types_are_not_changed_on_round_trip
+ topic = Topic.new(parent_id: "123")
+ assert_equal "123", topic.parent_id_before_type_cast
+ assert_equal "123", YAML.load(YAML.dump(topic)).parent_id_before_type_cast
+ end
+
+ def test_cast_types_are_not_changed_on_round_trip
+ topic = Topic.new(parent_id: "123")
+ assert_equal 123, topic.parent_id
+ assert_equal 123, YAML.load(YAML.dump(topic)).parent_id
+ end
+
+ def test_new_records_remain_new_after_round_trip
+ topic = Topic.new
+
+ assert topic.new_record?, "Sanity check that new records are new"
+ assert YAML.load(YAML.dump(topic)).new_record?, "Record should be new after deserialization"
+
+ topic.save!
+
+ assert_not topic.new_record?, "Saved records are not new"
+ assert_not YAML.load(YAML.dump(topic)).new_record?, "Saved record should not be new after deserialization"
+
+ topic = Topic.select('title').last
+
+ assert_not topic.new_record?, "Loaded records without ID are not new"
+ assert_not YAML.load(YAML.dump(topic)).new_record?, "Record should not be new after deserialization"
+ end
+
+ def test_types_of_virtual_columns_are_not_changed_on_round_trip
+ author = Author.select('authors.*, count(posts.id) as posts_count')
+ .joins(:posts)
+ .group('authors.id')
+ .first
+ dumped = YAML.load(YAML.dump(author))
+
+ assert_equal 5, author.posts_count
+ assert_equal 5, dumped.posts_count
+ end
end
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/books.yml b/activerecord/test/fixtures/books.yml
index fb48645456..abe56752c6 100644
--- a/activerecord/test/fixtures/books.yml
+++ b/activerecord/test/fixtures/books.yml
@@ -2,8 +2,10 @@ awdr:
author_id: 1
id: 1
name: "Agile Web Development with Rails"
+ format: "paperback"
rfr:
author_id: 1
id: 2
name: "Ruby for Rails"
+ format: "ebook"
diff --git a/activerecord/test/fixtures/topics.yml b/activerecord/test/fixtures/topics.yml
index bf049abbf1..4c98b10380 100644
--- a/activerecord/test/fixtures/topics.yml
+++ b/activerecord/test/fixtures/topics.yml
@@ -6,7 +6,7 @@ first:
written_on: 2003-07-16t15:28:11.2233+01:00
last_read: 2004-04-15
bonus_time: 2005-01-30t15:28:00.00+01:00
- content: Have a nice day
+ content: "--- Have a nice day\n...\n"
approved: false
replies_count: 1
@@ -15,7 +15,7 @@ second:
title: The Second Topic of the day
author_name: Mary
written_on: 2004-07-15t15:28:00.0099+01:00
- content: Have a nice day
+ content: "--- Have a nice day\n...\n"
approved: true
replies_count: 0
parent_id: 1
@@ -26,7 +26,7 @@ third:
title: The Third Topic of the day
author_name: Carl
written_on: 2012-08-12t20:24:22.129346+00:00
- content: I'm a troll
+ content: "--- I'm a troll\n...\n"
approved: true
replies_count: 1
@@ -35,7 +35,7 @@ fourth:
title: The Fourth Topic of the day
author_name: Carl
written_on: 2006-07-15t15:28:00.0099+01:00
- content: Why not?
+ content: "--- Why not?\n...\n"
approved: true
type: Reply
parent_id: 3
@@ -45,5 +45,5 @@ fifth:
title: The Fifth Topic of the day
author_name: Jason
written_on: 2013-07-13t12:11:00.0099+01:00
- content: Omakase
+ content: "--- Omakase\n...\n"
approved: true
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/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/face.rb b/activerecord/test/models/face.rb
index edb75d333f..3d7f0626e2 100644
--- a/activerecord/test/models/face.rb
+++ b/activerecord/test/models/face.rb
@@ -1,6 +1,7 @@
class Face < ActiveRecord::Base
belongs_to :man, :inverse_of => :face
belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_face
+ belongs_to :polymorphic_man_without_inverse, :polymorphic => true
# These is a "broken" inverse_of for the purposes of testing
belongs_to :horrible_man, :class_name => 'Man', :inverse_of => :horrible_face
belongs_to :horrible_polymorphic_man, :polymorphic => true, :inverse_of => :horrible_polymorphic_face
diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb
index f4d127730c..a26491ce61 100644
--- a/activerecord/test/models/man.rb
+++ b/activerecord/test/models/man.rb
@@ -1,6 +1,7 @@
class Man < ActiveRecord::Base
has_one :face, :inverse_of => :man
has_one :polymorphic_face, :class_name => 'Face', :as => :polymorphic_man, :inverse_of => :polymorphic_man
+ has_one :polymorphic_face_without_inverse, :class_name => 'Face', :as => :polymorphic_man_without_inverse
has_many :interests, :inverse_of => :man
has_many :polymorphic_interests, :class_name => 'Interest', :as => :polymorphic_man, :inverse_of => :polymorphic_man
# These are "broken" inverse_of associations for the purposes of testing
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index cf24502d3a..2e3a9a3681 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -3,6 +3,18 @@ class Owner < ActiveRecord::Base
has_many :pets, -> { order 'pets.name desc' }
has_many :toys, :through => :pets
+ belongs_to :last_pet, class_name: 'Pet'
+ scope :including_last_pet, -> {
+ select(%q[
+ owners.*, (
+ select p.pet_id from pets p
+ where p.owner_id = owners.owner_id
+ order by p.name desc
+ limit 1
+ ) as last_pet_id
+ ]).includes(:last_pet)
+ }
+
after_commit :execute_blocks
def blocks
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index e472cf951d..90a3c3ecee 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -84,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/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 4fcbf4dbd2..e9294a11b9 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,6 +1,6 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees
+ %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_uuids postgresql_ltrees
postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_citext).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -118,13 +118,6 @@ _SQL
end
execute <<_SQL
- CREATE TABLE postgresql_moneys (
- id SERIAL PRIMARY KEY,
- wealth MONEY
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_numbers (
id SERIAL PRIMARY KEY,
single REAL,
@@ -150,14 +143,6 @@ _SQL
_SQL
execute <<_SQL
- CREATE TABLE postgresql_bit_strings (
- id SERIAL PRIMARY KEY,
- bit_string BIT(8),
- bit_string_varying BIT VARYING(8)
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_oids (
id SERIAL PRIMARY KEY,
obj_id OID
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index da3074e90f..a7c7fc70bc 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -9,14 +9,6 @@ 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"
- end
when "PostgreSQL"
enable_uuid_ossp!(ActiveRecord::Base.connection)
create_table :uuid_parents, id: :uuid, force: true do |t|
@@ -62,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
@@ -103,6 +103,7 @@ ActiveRecord::Schema.define do
create_table :books, force: true do |t|
t.integer :author_id
+ t.string :format
t.column :name, :string
t.column :status, :integer, default: 0
t.column :read_status, :integer, default: 0
@@ -187,6 +188,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|
@@ -385,6 +389,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
@@ -522,6 +529,7 @@ ActiveRecord::Schema.define do
t.references :best_friend
t.references :best_friend_of
t.integer :insures, null: false, default: 0
+ t.timestamp :born_at
t.timestamps
end
@@ -767,6 +775,8 @@ ActiveRecord::Schema.define do
t.integer :man_id
t.integer :polymorphic_man_id
t.string :polymorphic_man_type
+ t.integer :polymorphic_man_without_inverse_id
+ t.string :polymorphic_man_without_inverse_type
t.integer :horrible_polymorphic_man_id
t.string :horrible_polymorphic_man_type
end
@@ -848,6 +858,13 @@ ActiveRecord::Schema.define do
execute "ALTER TABLE lessons_students ADD CONSTRAINT student_id_fk FOREIGN KEY (#{quote_column_name 'student_id'}) REFERENCES #{quote_table_name 'students'} (#{quote_column_name 'id'})"
end
+
+ create_table :overloaded_types, force: true do |t|
+ t.float :overloaded_float, default: 500
+ t.float :unoverloaded_float
+ t.string :overloaded_string_with_limit, limit: 255
+ t.string :string_with_default, default: 'the original default'
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activerecord/test/support/schema_dumping_helper.rb b/activerecord/test/support/schema_dumping_helper.rb
new file mode 100644
index 0000000000..2ae8d299e5
--- /dev/null
+++ b/activerecord/test/support/schema_dumping_helper.rb
@@ -0,0 +1,11 @@
+module SchemaDumpingHelper
+ def dump_table_schema(table, connection = ActiveRecord::Base.connection)
+ old_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
+ ActiveRecord::SchemaDumper.ignore_tables = connection.tables - [table]
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ stream.string
+ ensure
+ ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables
+ end
+end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 5e430d20fa..5962dd255c 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,126 @@
+* Fixed `ActiveSupport::Cache::FileStore` exploding with long paths.
+ *Adam Panzer / Michael Grosser*
+
+* Fixed `ActiveSupport::TimeWithZone#-` so precision is not unnecessarily lost
+ when working with objects with a nanosecond component.
+
+ `ActiveSupport::TimeWithZone#-` should return the same result as if we were
+ using `Time#-`:
+
+ Time.now.end_of_day - Time.now.beginning_of_day #=> 86399.999999999
+
+ Before:
+
+ Time.zone.now.end_of_day.nsec #=> 999999999
+ Time.zone.now.end_of_day - Time.zone.now.beginning_of_day #=> 86400.0
+
+ After:
+
+ Time.zone.now.end_of_day - Time.zone.now.beginning_of_day
+ #=> 86399.999999999
+
+ *Gordon Chan*
+
+* Fixed precision error in NumberHelper when using Rationals.
+
+ before:
+ ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
+ #=> "330.00"
+ after:
+ ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2
+ #=> "333.33"
+
+ See #15379.
+
+ *Juanjo Bazán*
+
+* Removed deprecated `Numeric#ago` and friends
+
+ Replacements:
+
+ 5.ago => 5.seconds.ago
+ 5.until => 5.seconds.until
+ 5.since => 5.seconds.since
+ 5.from_now => 5.seconds.from_now
+
+ See #12389 for the history and rationale behind this.
+
+ *Godfrey Chan*
+
+* DateTime `advance` now supports partial days.
+
+ Before:
+
+ DateTime.now.advance(days: 1, hours: 12)
+
+ After:
+
+ DateTime.now.advance(days: 1.5)
+
+ Fixes #12005.
+
+ *Shay Davidson*
+
+* `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.
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index f3582767c0..a6424a353a 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -30,6 +30,11 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
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/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 8ed60aebac..d08ecd2f7d 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -14,6 +14,7 @@ module ActiveSupport
DIR_FORMATTER = "%03X"
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
+ FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
EXCLUDED_DIRS = ['.', '..'].freeze
def initialize(cache_path, options = nil)
@@ -117,6 +118,10 @@ module ActiveSupport
# Translate a key into a file path.
def key_file_path(key)
+ if key.size > FILEPATH_MAX_SIZE
+ key = Digest::MD5.hexdigest(key)
+ end
+
fname = URI.encode_www_form_component(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 05ca943776..06505bddf9 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -297,14 +297,14 @@ module ActiveSupport
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
@@ -724,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/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 67f58bc0fe..caa499dfa2 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -5,6 +5,8 @@ class Array
# %w( a b c d ).from(2) # => ["c", "d"]
# %w( a b c d ).from(10) # => []
# %w().from(0) # => []
+ # %w( a b c d ).from(-2) # => ["c", "d"]
+ # %w( a b c ).from(-10) # => []
def from(position)
self[position, length] || []
end
@@ -15,8 +17,10 @@ class Array
# %w( a b c d ).to(2) # => ["a", "b", "c"]
# %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
# %w().to(0) # => []
+ # %w( a b c d ).to(-2) # => ["a", "b", "c"]
+ # %w( a b c ).to(-10) # => []
def to(position)
- first position + 1
+ self[0..position]
end
# Equal to <tt>self[1]</tt>.
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 73ad0aa097..289ca12b5e 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -53,6 +53,16 @@ class DateTime
# <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>,
# <tt>:minutes</tt>, <tt>:seconds</tt>.
def advance(options)
+ unless options[:weeks].nil?
+ options[:weeks], partial_weeks = options[:weeks].divmod(1)
+ options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
+ end
+
+ unless options[:days].nil?
+ options[:days], partial_days = options[:days].divmod(1)
+ options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
+ end
+
d = to_date.advance(options)
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
seconds_to_advance = \
@@ -63,7 +73,7 @@ class DateTime
if seconds_to_advance.zero?
datetime_advanced_by_date
else
- datetime_advanced_by_date.since seconds_to_advance
+ datetime_advanced_by_date.since(seconds_to_advance)
end
end
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/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 970d6faa1d..28cb3e2a3b 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -18,6 +18,6 @@ class Hash
#
# b = { b: 1 }
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
- # # => {"b"=>32}
+ # # => {"b"=>1}
alias nested_under_indifferent_access with_indifferent_access
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..5934c578ea 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -27,7 +27,7 @@ class Hash
# hash = { name: 'Rob', age: '28' }
#
# hash.stringify_keys
- # # => { "name" => "Rob", "age" => "28" }
+ # # => {"name"=>"Rob", "age"=>"28"}
def stringify_keys
transform_keys{ |key| key.to_s }
end
@@ -44,7 +44,7 @@ class Hash
# hash = { 'name' => 'Rob', 'age' => '28' }
#
# hash.symbolize_keys
- # # => { name: "Rob", age: "28" }
+ # # => {:name=>"Rob", :age=>"28"}
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
@@ -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/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/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 704c4248d9..689fae4830 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -61,25 +61,7 @@ class Numeric
end
alias :fortnight :fortnights
- # Reads best without arguments: 10.minutes.ago
- def ago(time = ::Time.current)
- ActiveSupport::Deprecation.warn "Calling #ago or #until on a number (e.g. 5.ago) is deprecated and will be removed in the future, use 5.seconds.ago instead"
- time - self
- end
-
- # Reads best with argument: 10.minutes.until(time)
- alias :until :ago
-
- # Reads best with argument: 10.minutes.since(time)
- def since(time = ::Time.current)
- ActiveSupport::Deprecation.warn "Calling #since or #from_now on a number (e.g. 5.since) is deprecated and will be removed in the future, use 5.seconds.since instead"
- time + self
- end
-
- # Reads best without arguments: 10.minutes.from_now
- alias :from_now :since
-
- # Used with the standard time durations, like 1.hour.in_milliseconds --
+ # Used with the standard time durations, like 1.hour.in_milliseconds --
# so we can feed them to JavaScript functions like getTime().
def in_milliseconds
self * 1000
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 3d2c809c9f..c5d59128e5 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, and number objects;
+ # False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects;
# true otherwise.
def duplicable?
true
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/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 18273573e0..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
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 2c8995be9a..46cd170c1d 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -19,12 +19,7 @@ class ERB
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
- s = s.to_s
- if s.html_safe?
- s
- else
- s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE).html_safe
- end
+ unwrapped_html_escape(s).html_safe
end
# Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
@@ -36,6 +31,18 @@ class ERB
singleton_class.send(:remove_method, :html_escape)
module_function :html_escape
+ # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
+ # This method is not for public consumption! Seriously!
+ def unwrapped_html_escape(s) # :nodoc:
+ s = s.to_s
+ if s.html_safe?
+ s
+ else
+ s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ end
+ end
+ module_function :unwrapped_html_escape
+
# A utility method for escaping HTML without affecting existing escaped entities.
#
# html_escape_once('1 < 2 &amp; 3')
@@ -170,13 +177,15 @@ module ActiveSupport #:nodoc:
self[0, 0]
end
- %w[concat prepend].each do |method_name|
- define_method method_name do |value|
- super(html_escape_interpolated_argument(value))
- end
+ def concat(value)
+ super(html_escape_interpolated_argument(value))
end
alias << concat
+ def prepend(value)
+ super(html_escape_interpolated_argument(value))
+ end
+
def prepend!(value)
ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend
prepend value
@@ -231,7 +240,8 @@ module ActiveSupport #:nodoc:
private
def html_escape_interpolated_argument(arg)
- (!html_safe? || arg.html_safe?) ? arg : ERB::Util.h(arg)
+ (!html_safe? || arg.html_safe?) ? arg :
+ arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
end
end
end
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/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 59675d744e..8a545e4386 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -180,10 +180,11 @@ module ActiveSupport #:nodoc:
Dependencies.load_missing_constant(from_mod, const_name)
end
- # Dependencies assumes the name of the module reflects the nesting (unless
- # it can be proven that is not the case), and the path to the file that
- # defines the constant. Anonymous modules cannot follow these conventions
- # and we assume therefore the user wants to refer to a top-level constant.
+ # We assume that the name of the module reflects the nesting
+ # (unless it can be proven that is not the case) and the path to the file
+ # that defines the constant. Anonymous modules cannot follow these
+ # conventions and therefore we assume that the user wants to refer to a
+ # top-level constant.
def guess_for_anonymous(const_name)
if Object.const_defined?(const_name)
raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module"
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 09eb732ef7..0ae641d05b 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -105,8 +105,7 @@ module ActiveSupport
# We define it as a workaround to Ruby 2.0.0-p353 bug.
# For more information, check rails/rails#13055.
- # It should be dropped once a new Ruby patch-level
- # release after 2.0.0-p353 happens.
+ # Remove it when we drop support for 2.0.0-p353.
def ===(other) #:nodoc:
value === other
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index a4ebdea598..e1eb81b8bc 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -57,7 +57,7 @@ module ActiveSupport
def initialize(constructor = {})
if constructor.respond_to?(:to_hash)
super()
- update(constructor.to_hash)
+ update(constructor)
else
super(constructor)
end
@@ -176,7 +176,14 @@ module ActiveSupport
indices.collect { |key| self[convert_key(key)] }
end
- # Returns an exact copy of the hash.
+ # Returns a shallow copy of the hash.
+ #
+ # hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })
+ # dup = hash.dup
+ # dup[:a][:c] = 'c'
+ #
+ # hash[:a][:c] # => nil
+ # dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
new_hash.default = default
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 6229d15619..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(/.*\./, '')))
@@ -173,7 +193,7 @@ module ActiveSupport
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
# 'Inflections'.demodulize # => "Inflections"
# '::Inflections'.demodulize # => "Inflections"
- # ''.demodulize # => ''
+ # ''.demodulize # => ""
#
# See also +deconstantize+.
def demodulize(path)
@@ -230,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.
@@ -244,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/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 7a96c66626..325a3d75dc 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -141,6 +141,11 @@ module ActiveSupport
#
# ActiveSupport::Notifications.unsubscribe(subscriber)
#
+ # You can also unsubscribe by passing the name of the subscriber object. Note
+ # that this will unsubscribe all subscriptions with the given name:
+ #
+ # ActiveSupport::Notifications.unsubscribe("render")
+ #
# == Default Queue
#
# Notifications ships with a queue implementation that consumes and publishes events
@@ -173,8 +178,8 @@ module ActiveSupport
unsubscribe(subscriber)
end
- def unsubscribe(args)
- notifier.unsubscribe(args)
+ def unsubscribe(subscriber_or_name)
+ notifier.unsubscribe(subscriber_or_name)
end
def instrumenter
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 8f5fa646e8..6bf8c7d5de 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -25,9 +25,15 @@ module ActiveSupport
subscriber
end
- def unsubscribe(subscriber)
+ def unsubscribe(subscriber_or_name)
synchronize do
- @subscribers.reject! { |s| s.matches?(subscriber) }
+ case subscriber_or_name
+ when String
+ @subscribers.reject! { |s| s.matches?(subscriber_or_name) }
+ else
+ @subscribers.delete(subscriber_or_name)
+ end
+
@listeners_for.clear
end
end
@@ -97,12 +103,11 @@ module ActiveSupport
end
def subscribed_to?(name)
- @pattern === name.to_s
+ @pattern === name
end
- def matches?(subscriber_or_name)
- self === subscriber_or_name ||
- @pattern && @pattern === subscriber_or_name
+ def matches?(name)
+ @pattern && @pattern === name
end
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index b169e3af01..5ecda9593a 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -232,12 +232,8 @@ module ActiveSupport
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
# number_to_human_size(483989, precision: 2) # => 470 KB
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
- #
- # Non-significant zeros after the fractional separator are stripped out by
- # default (set <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
- #
- # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
- # number_to_human_size(524288000, precision: 5) # => "500 MB"
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
NumberToHumanSizeConverter.convert(number, options)
end
@@ -305,12 +301,15 @@ module ActiveSupport
# separator: ',',
# significant: false) # => "1,2 Million"
#
+ # number_to_human(500000000, precision: 5) # => "500 Million"
+ # number_to_human(12345012345, significant: false) # => "12.345 Billion"
+ #
# Non-significant zeros after the decimal separator are stripped
# out by default (set <tt>:strip_insignificant_zeros</tt> to
# +false+ to change that):
#
- # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
- # number_to_human(500000000, precision: 5) # => "500 Million"
+ # number_to_human(12.00001) # => "12"
+ # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
#
# ==== Custom Unit Quantifiers
#
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/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index c45f6cdcfa..01597b288a 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -12,11 +12,7 @@ module ActiveSupport
when Float, String
@number = BigDecimal(number.to_s)
when Rational
- if significant
- @number = BigDecimal(number, digit_count(number.to_i) + precision)
- else
- @number = BigDecimal(number, precision)
- end
+ @number = BigDecimal(number, digit_count(number.to_i) + precision)
else
@number = number.to_d
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/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 2fb5c04316..e6c125bfdd 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,5 +1,3 @@
-gem 'minitest' # make sure we get the gem, not stdlib
-require 'minitest'
require 'active_support/testing/tagged_logging'
require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 76a591bc3b..11cca82995 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -9,7 +9,7 @@ module ActiveSupport
#
# assert_not nil # => true
# assert_not false # => true
- # assert_not 'foo' # => 'foo' is not nil or false
+ # assert_not 'foo' # => Expected "foo" to be nil or false
#
# An error message can be specified.
#
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
index e709e6edf5..0bf3643a56 100644
--- a/activesupport/lib/active_support/testing/declarative.rb
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -1,30 +1,6 @@
module ActiveSupport
module Testing
module Declarative
-
- def self.extended(klass) #:nodoc:
- klass.class_eval do
-
- unless method_defined?(:describe)
- def self.describe(text)
- if block_given?
- super
- else
- message = "`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n"
- ActiveSupport::Deprecation.warn message
-
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.name
- "#{text}"
- end
- RUBY_EVAL
- end
- end
- end
-
- end
- end
-
unless defined?(Spec)
# Helper to define a test method using a String. Under the hood, it replaces
# spaces with underscores and defines the test method.
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 92a593965e..ea2d3391bd 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -1,5 +1,3 @@
-require 'active_support'
-
module ActiveSupport
autoload :Duration, 'active_support/duration'
autoload :TimeWithZone, 'active_support/time_with_zone'
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index c25c97cfa8..4a0ed356b1 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)
@@ -259,7 +262,7 @@ module ActiveSupport
# If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
# otherwise move backwards #utc, for accuracy when moving across DST boundaries
if other.acts_like?(:time)
- utc.to_f - other.to_f
+ to_time - other.to_time
elsif duration_of_variable_length?(other)
method_missing(:-, other)
else
@@ -350,6 +353,14 @@ module ActiveSupport
initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc)
end
+ # respond_to_missing? is not called in some cases, such as when type conversion is
+ # performed with Kernel#String
+ def respond_to?(sym, include_priv = false)
+ # ensure that we're not going to throw and rescue from NoMethodError in method_missing which is slow
+ return false if sym.to_sym == :to_str
+ super
+ end
+
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
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/caching_test.rb b/activesupport/test/caching_test.rb
index 18923f61d1..d55cc5d3b0 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -692,6 +692,11 @@ class FileStoreTest < ActiveSupport::TestCase
assert File.exist?(filepath)
end
+ def test_long_keys
+ @cache.write("a"*10000, 1)
+ assert_equal 1, @cache.read("a"*10000)
+ end
+
def test_key_transformation
key = @cache.send(:key_file_path, "views/index?id=1")
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index bbeb710a0c..8a9fd4996b 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -27,13 +27,24 @@ module ConstantizeTestCases
assert_equal Ace::Base::Case, yield("Ace::Base::Case")
assert_equal Ace::Base::Case, yield("::Ace::Base::Case")
assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice")
+ assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Fase::Dice")
assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice")
+
assert_equal Ace::Gas::Case, yield("Ace::Gas::Case")
assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice")
+ assert_equal Ace::Base::Case::Dice, yield("Ace::Gas::Case::Dice")
+
assert_equal Case::Dice, yield("Case::Dice")
+ assert_equal AddtlGlobalConstants::Case::Dice, yield("Case::Dice")
+ assert_equal Object::AddtlGlobalConstants::Case::Dice, yield("Case::Dice")
+
assert_equal Case::Dice, yield("Object::Case::Dice")
+ assert_equal AddtlGlobalConstants::Case::Dice, yield("Object::Case::Dice")
+ assert_equal Object::AddtlGlobalConstants::Case::Dice, yield("Case::Dice")
+
assert_equal ConstantizeTestCases, yield("ConstantizeTestCases")
assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases")
+
assert_raises(NameError) { yield("UnknownClass") }
assert_raises(NameError) { yield("UnknownClass::Ace") }
assert_raises(NameError) { yield("UnknownClass::Ace::Base") }
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 57722fd52a..bd1b818717 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -1,22 +1,25 @@
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
assert_equal %w( a b c d ), %w( a b c d ).from(0)
assert_equal %w( c d ), %w( a b c d ).from(2)
assert_equal %w(), %w( a b c d ).from(10)
+ assert_equal %w( d e ), %w( a b c d e ).from(-2)
+ assert_equal %w(), %w( a b c d e ).from(-10)
end
def test_to
assert_equal %w( a ), %w( a b c d ).to(0)
assert_equal %w( a b c ), %w( a b c d ).to(2)
assert_equal %w( a b c d ), %w( a b c d ).to(10)
+ assert_equal %w( a b c ), %w( a b c d ).to(-2)
+ assert_equal %w(), %w( a b c ).to(-10)
end
def test_second_through_tenth
@@ -234,7 +237,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 +252,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 +288,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 +320,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/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 0a40aeb96c..224172e39f 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -162,6 +162,12 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
end
+ def test_advance_partial_days
+ assert_equal DateTime.civil(2012,9,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5)
+ assert_equal DateTime.civil(2012,9,28,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 0.5)
+ assert_equal DateTime.civil(2012,10,29,13,15,10), DateTime.civil(2012,9,28,1,15,10).advance(:days => 1.5, :months => 1)
+ end
+
def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas
# If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead.
assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1)
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index d824a16e98..f18172a5ce 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
@@ -66,6 +70,8 @@ class HashExtTest < ActiveSupport::TestCase
assert_respond_to h, :to_options!
assert_respond_to h, :compact
assert_respond_to h, :compact!
+ assert_respond_to h, :except
+ assert_respond_to h, :except!
end
def test_transform_keys
@@ -84,6 +90,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 +118,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 +146,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 +174,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 +222,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 +250,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,6 +659,14 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 1, h['first']
end
+ def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
+ h = HashWithIndifferentAccess.new({ a: { b: 'b' } })
+ dup = h.dup
+
+ dup[:a][:c] = 'c'
+ assert_equal 'c', h[:a][:c]
+ end
+
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 +729,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" } } })
@@ -887,13 +929,19 @@ class HashExtTest < ActiveSupport::TestCase
def test_except_with_more_than_one_argument
original = { :a => 'x', :b => 'y', :c => 10 }
expected = { :a => 'x' }
+
assert_equal expected, original.except(:b, :c)
+
+ assert_equal expected, original.except!(:b, :c)
+ assert_equal expected, original
end
def test_except_with_original_frozen
original = { :a => 'x', :b => 'y' }
original.freeze
assert_nothing_raised { original.except(:a) }
+
+ assert_raise(RuntimeError) { original.except!(:a) }
end
def test_except_with_mocha_expectation_on_original
@@ -905,11 +953,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 +966,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/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/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 3b1dabea8d..dbc3ffd319 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -22,18 +22,6 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
end
- def test_deprecated_since_and_ago
- assert_equal @now + 1, assert_deprecated { 1.since(@now) }
- assert_equal @now - 1, assert_deprecated { 1.ago(@now) }
- end
-
- def test_deprecated_since_and_ago_without_argument
- now = Time.now
- assert assert_deprecated { 1.since } >= now + 1
- now = Time.now
- assert assert_deprecated { 1.ago } >= now - 1
- end
-
def test_irregular_durations
assert_equal @now.advance(:days => 3000), 3000.days.since(@now)
assert_equal @now.advance(:months => 1), 1.month.since(@now)
@@ -84,36 +72,6 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10) + 1.year
end
- def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set
- Time.zone = nil
- with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
- assert_equal Time.local(2000,1,1,0,0,5), assert_deprecated { 5.since }
- # ago
- assert_not_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
- assert_equal Time.local(1999,12,31,23,59,55), assert_deprecated { 5.ago }
- end
- end
-
- def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set
- Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.since }
- assert_equal Time.utc(2000,1,1,0,0,5), assert_deprecated { 5.since.time }
- assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.since.time_zone.name }
- # ago
- assert_instance_of ActiveSupport::TimeWithZone, assert_deprecated { 5.ago }
- assert_equal Time.utc(1999,12,31,23,59,55), assert_deprecated { 5.ago.time }
- assert_equal 'Eastern Time (US & Canada)', assert_deprecated { 5.ago.time_zone.name }
- end
- ensure
- Time.zone = nil
- end
-
protected
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
@@ -435,7 +393,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal BigDecimal, BigDecimal("1000010").class
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
-
+
def test_in_milliseconds
assert_equal 10_000, 10.seconds.in_milliseconds
end
diff --git a/activesupport/test/core_ext/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 246bc7fa61..246bc7fa61 100644
--- a/activesupport/test/core_ext/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index b054a8dd31..32d512eca3 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -37,11 +37,14 @@ class InTest < ActiveSupport::TestCase
end
class C < B
end
+ class D
+ end
def test_in_module
assert A.in?(B)
assert A.in?(C)
assert !A.in?(A)
+ assert !A.in?(D)
end
def test_no_method_catching
@@ -51,5 +54,6 @@ class InTest < ActiveSupport::TestCase
def test_presence_in
assert_equal "stuff", "stuff".presence_in(%w( lots of stuff ))
assert_nil "stuff".presence_in(%w( lots of crap ))
+ assert_raise(ArgumentError) { 1.presence_in(1) }
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/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index f8d4d0f0dc..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)
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 7fe4d4a6b2..6d779bf3d5 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -246,16 +246,31 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 1)
end
+ def test_minus_with_time_precision
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['Hawaii'] ) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ end
+
def test_minus_with_time_with_zone
twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] )
twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] )
assert_equal 86_400.0, twz2 - twz1
end
+ def test_minus_with_time_with_zone_precision
+ twz1 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone['UTC'] )
+ twz2 = ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] )
+ assert_equal 86_399.999999998, twz2 - twz1
+ end
+
def test_minus_with_datetime
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
end
+ def test_minus_with_datetime_precision
+ assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
+ end
+
def test_minus_with_wrapped_datetime
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - Time.utc(2000, 1, 1)
assert_equal 86_400.0, ActiveSupport::TimeWithZone.new( DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone['UTC'] ) - DateTime.civil(2000, 1, 1)
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
index 5ef59b6e6b..3faa15e7fd 100644
--- a/activesupport/test/i18n_test.rb
+++ b/activesupport/test/i18n_test.rb
@@ -99,7 +99,6 @@ class I18nTest < ActiveSupport::TestCase
end
def test_to_sentence_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
assert_equal 'a, b, and c', %w[a b c].to_sentence(locale: 'empty')
end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index b0b4738eb3..eb8b0d878e 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -498,10 +498,10 @@ class InflectorTest < ActiveSupport::TestCase
end
%w(plurals singulars uncountables humans acronyms).each do |scope|
- ActiveSupport::Inflector.inflections do |inflect|
- define_method("test_clear_inflections_with_#{scope}") do
- with_dup do
- # clear the inflections
+ define_method("test_clear_inflections_with_#{scope}") do
+ with_dup do
+ # clear the inflections
+ ActiveSupport::Inflector.inflections do |inflect|
inflect.clear(scope)
assert_equal [], inflect.send(scope)
end
@@ -516,9 +516,10 @@ class InflectorTest < ActiveSupport::TestCase
# there are module functions that access ActiveSupport::Inflector.inflections,
# so we need to replace the singleton itself.
def with_dup
- original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup)
+ original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
+ yield
ensure
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original)
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
end
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_test.rb b/activesupport/test/multibyte_conformance_test.rb
index 2baf724da4..6ab8fa28ee 100644
--- a/activesupport/test/multibyte_conformance_test.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_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
index 65aecece71..e6925e9083 100644
--- a/activesupport/test/number_helper_i18n_test.rb
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -43,6 +43,10 @@ module ActiveSupport
:custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
end
+ def teardown
+ I18n.backend.reload!
+ end
+
def test_number_to_i18n_currency
assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts'))
assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts'))
@@ -50,8 +54,6 @@ module ActiveSupport
end
def test_number_to_currency_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("$10.00", number_to_currency(10, :locale => 'empty'))
assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty'))
end
@@ -80,8 +82,6 @@ module ActiveSupport
end
def test_number_with_i18n_precision_and_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("123456789.123", number_to_rounded(123456789.123456789, :locale => 'empty'))
assert_equal("1.000", number_to_rounded(1.0000, :locale => 'empty'))
end
@@ -92,8 +92,6 @@ module ActiveSupport
end
def test_number_with_i18n_delimiter_and_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'empty'))
end
@@ -107,8 +105,6 @@ module ActiveSupport
end
def test_number_to_i18n_percentage_and_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("1.000%", number_to_percentage(1, :locale => 'empty'))
assert_equal("1.243%", number_to_percentage(1.2434, :locale => 'empty'))
assert_equal("12434.000%", number_to_percentage(12434, :locale => 'empty'))
@@ -121,8 +117,6 @@ module ActiveSupport
end
def test_number_to_i18n_human_size_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal("2 KB", number_to_human_size(2048, :locale => 'empty'))
assert_equal("42 Bytes", number_to_human_size(42, :locale => 'empty'))
end
@@ -142,8 +136,6 @@ module ActiveSupport
end
def test_number_to_human_with_empty_i18n_store
- I18n.backend.store_translations 'empty', {}
-
assert_equal "2 Thousand", number_to_human(2000, :locale => 'empty')
assert_equal "1.23 Billion", number_to_human(1234567890, :locale => 'empty')
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 9bdb92024e..bb51cc68f2 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
@@ -132,6 +134,7 @@ module ActiveSupport
assert_equal("111.23460000000000000000", number_helper.number_to_rounded('111.2346', :precision => 20))
assert_equal("111.23460000000000000000", number_helper.number_to_rounded(BigDecimal(111.2346, Float::DIG), :precision => 20))
assert_equal("111.2346" + "0"*96, number_helper.number_to_rounded('111.2346', :precision => 100))
+ assert_equal("111.2346", number_helper.number_to_rounded(Rational(1112346, 10000), :precision => 4))
end
end
@@ -172,6 +175,7 @@ module ActiveSupport
assert_equal "9775.0000000000000000", number_helper.number_to_rounded(BigDecimal(9775), :precision => 20, :significant => true )
assert_equal "9775.0000000000000000", number_helper.number_to_rounded("9775", :precision => 20, :significant => true )
assert_equal "9775." + "0"*96, number_helper.number_to_rounded("9775", :precision => 100, :significant => true )
+ assert_equal("97.7", number_helper.number_to_rounded(Rational(9772, 100), :precision => 3, :significant => true))
end
end
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
index 253411aa3d..21e4ba0cee 100644
--- a/activesupport/test/subscriber_test.rb
+++ b/activesupport/test/subscriber_test.rb
@@ -4,20 +4,28 @@ 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
+ remove_method :open_party
+ def open_party(event)
+ events << event
end
end
@@ -29,12 +37,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/test_test.rb b/activesupport/test/test_test.rb
index 0fa08c0e3a..6f63a8a725 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -28,12 +28,30 @@ class AssertDifferenceTest < ActiveSupport::TestCase
assert_equal 'custom', e.message
end
- def test_assert_no_difference
+ def test_assert_no_difference_pass
assert_no_difference '@object.num' do
# ...
end
end
+ def test_assert_no_difference_fail
+ error = assert_raises(Minitest::Assertion) do
+ assert_no_difference '@object.num' do
+ @object.increment
+ end
+ end
+ assert_equal "\"@object.num\" didn't change by 0.\nExpected: 0\n Actual: 1", error.message
+ end
+
+ def test_assert_no_difference_with_message_fail
+ error = assert_raises(Minitest::Assertion) do
+ assert_no_difference '@object.num', 'Object Changed' do
+ @object.increment
+ end
+ end
+ assert_equal "Object Changed.\n\"@object.num\" didn't change by 0.\nExpected: 0\n Actual: 1", error.message
+ end
+
def test_assert_difference
assert_difference '@object.num', +1 do
@object.increment
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/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 14ad4fd424..4cd4a9d70c 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,3 +1,21 @@
+* Change Posts to Articles in Getting Started sample application in order to
+better align with the actual guides.
+
+ * John Kelly Ferguson*
+
+* Update all Rails 4.1.0 references to 4.1.1 within the guides and code.
+
+ * John Kelly Ferguson*
+
+* Split up rows in the Explain Queries table of the ActiveRecord Querying section
+in order to improve readability.
+
+ * John Kelly Ferguson*
+
+* Change all non-HTTP method 'post' references to 'article'.
+
+ *John Kelly Ferguson*
+
* Updates the maintenance policy to match the latest versions of Rails
*Matias Korhonen*
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/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 2435444dc9..d95354e12d 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -2,6 +2,7 @@ unless File.exist?('Gemfile')
File.write('Gemfile', <<-GEMFILE)
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
+ gem 'arel', github: 'rails/arel'
gem 'sqlite3'
GEMFILE
diff --git a/guides/code/getting_started/.gitignore b/guides/code/getting_started/.gitignore
deleted file mode 100644
index 6a502e997f..0000000000
--- a/guides/code/getting_started/.gitignore
+++ /dev/null
@@ -1,16 +0,0 @@
-# See https://help.github.com/articles/ignoring-files for more about ignoring files.
-#
-# If you find yourself ignoring temporary files generated by your text editor
-# or operating system, you probably want to add a global ignore instead:
-# git config --global core.excludesfile '~/.gitignore_global'
-
-# Ignore bundler config.
-/.bundle
-
-# Ignore the default SQLite database.
-/db/*.sqlite3
-/db/*.sqlite3-journal
-
-# Ignore all logfiles and tempfiles.
-/log/*.log
-/tmp
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
deleted file mode 100644
index c3d7e96c4d..0000000000
--- a/guides/code/getting_started/Gemfile
+++ /dev/null
@@ -1,40 +0,0 @@
-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
-gem 'sqlite3'
-# Use SCSS for stylesheets
-gem 'sass-rails', '~> 4.0.1'
-# Use Uglifier as compressor for JavaScript assets
-gem 'uglifier', '>= 1.3.0'
-# Use CoffeeScript for .js.coffee assets and views
-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
-gem 'jquery-rails'
-# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
-gem 'turbolinks'
-# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 2.0'
-# bundle exec rake doc:rails generates the API under doc/api.
-gem 'sdoc', '~> 0.4.0', group: :doc
-
-# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
-gem 'spring', group: :development
-
-# Use ActiveModel has_secure_password
-# gem 'bcrypt', '~> 3.1.7'
-
-# Use unicorn as the app server
-# gem 'unicorn'
-
-# Use Capistrano for deployment
-# gem 'capistrano-rails', group: :development
-
-# Use debugger
-# gem 'debugger', group: [:development, :test]
-
diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock
deleted file mode 100644
index a2ab76c908..0000000000
--- a/guides/code/getting_started/Gemfile.lock
+++ /dev/null
@@ -1,126 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- actionmailer (4.1.0)
- actionpack (= 4.1.0)
- actionview (= 4.1.0)
- mail (~> 2.5.4)
- actionpack (4.1.0)
- actionview (= 4.1.0)
- activesupport (= 4.1.0)
- rack (~> 1.5.2)
- rack-test (~> 0.6.2)
- actionview (4.1.0)
- activesupport (= 4.1.0)
- builder (~> 3.1)
- erubis (~> 2.7.0)
- activemodel (4.1.0)
- activesupport (= 4.1.0)
- builder (~> 3.1)
- activerecord (4.1.0)
- activemodel (= 4.1.0)
- activesupport (= 4.1.0)
- arel (~> 5.0.0)
- activesupport (4.1.0)
- i18n (~> 0.6, >= 0.6.9)
- json (~> 1.7, >= 1.7.7)
- minitest (~> 5.1)
- thread_safe (~> 0.1)
- tzinfo (~> 1.1)
- arel (5.0.0)
- atomic (1.1.14)
- builder (3.2.2)
- coffee-rails (4.0.1)
- coffee-script (>= 2.2.0)
- railties (>= 4.0.0, < 5.0)
- coffee-script (2.2.0)
- coffee-script-source
- execjs
- coffee-script-source (1.6.3)
- erubis (2.7.0)
- execjs (2.0.2)
- hike (1.2.3)
- i18n (0.6.9)
- jbuilder (2.0.2)
- activesupport (>= 3.0.0)
- multi_json (>= 1.2.0)
- jquery-rails (3.0.4)
- railties (>= 3.0, < 5.0)
- thor (>= 0.14, < 2.0)
- json (1.8.1)
- mail (2.5.4)
- mime-types (~> 1.16)
- treetop (~> 1.4.8)
- mime-types (1.25.1)
- minitest (5.2.1)
- multi_json (1.8.4)
- polyglot (0.3.3)
- rack (1.5.2)
- rack-test (0.6.2)
- rack (>= 1.0)
- rails (4.1.0)
- actionmailer (= 4.1.0)
- actionpack (= 4.1.0)
- actionview (= 4.1.0)
- activemodel (= 4.1.0)
- activerecord (= 4.1.0)
- activesupport (= 4.1.0)
- bundler (>= 1.3.0, < 2.0)
- railties (= 4.1.0)
- sprockets-rails (~> 2.0.0)
- railties (4.1.0)
- actionpack (= 4.1.0)
- activesupport (= 4.1.0)
- rake (>= 0.8.7)
- thor (>= 0.18.1, < 2.0)
- rake (10.1.1)
- rdoc (4.1.1)
- json (~> 1.4)
- sass (3.2.13)
- sass-rails (4.0.1)
- railties (>= 4.0.0, < 5.0)
- sass (>= 3.1.10)
- sprockets-rails (~> 2.0.0)
- sdoc (0.4.0)
- json (~> 1.8)
- rdoc (~> 4.0, < 5.0)
- spring (1.0.0)
- sprockets (2.10.1)
- hike (~> 1.2)
- multi_json (~> 1.0)
- rack (~> 1.0)
- tilt (~> 1.1, != 1.3.0)
- sprockets-rails (2.0.1)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- sprockets (~> 2.8)
- sqlite3 (1.3.8)
- thor (0.18.1)
- thread_safe (0.1.3)
- atomic
- tilt (1.4.1)
- treetop (1.4.15)
- polyglot
- polyglot (>= 0.3.1)
- turbolinks (2.2.0)
- coffee-rails
- tzinfo (1.1.0)
- thread_safe (~> 0.1)
- uglifier (2.4.0)
- execjs (>= 0.3.0)
- json (>= 1.8.0)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- coffee-rails (~> 4.0.0)
- jbuilder (~> 2.0)
- jquery-rails
- rails (= 4.1.0)
- sass-rails (~> 4.0.1)
- sdoc (~> 0.4.0)
- spring
- sqlite3
- turbolinks
- uglifier (>= 1.3.0)
diff --git a/guides/code/getting_started/README.rdoc b/guides/code/getting_started/README.rdoc
deleted file mode 100644
index dd4e97e22e..0000000000
--- a/guides/code/getting_started/README.rdoc
+++ /dev/null
@@ -1,28 +0,0 @@
-== README
-
-This README would normally document whatever steps are necessary to get the
-application up and running.
-
-Things you may want to cover:
-
-* Ruby version
-
-* System dependencies
-
-* Configuration
-
-* Database creation
-
-* Database initialization
-
-* How to run the test suite
-
-* Services (job queues, cache servers, search engines, etc.)
-
-* Deployment instructions
-
-* ...
-
-
-Please feel free to use a different markup language if you do not plan to run
-<tt>rake doc:app</tt>.
diff --git a/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile
deleted file mode 100644
index ba6b733dd2..0000000000
--- a/guides/code/getting_started/Rakefile
+++ /dev/null
@@ -1,6 +0,0 @@
-# Add your own tasks in files placed in lib/tasks ending in .rake,
-# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
-
-require File.expand_path('../config/application', __FILE__)
-
-Rails.application.load_tasks
diff --git a/guides/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js
deleted file mode 100644
index 5a4fbaa370..0000000000
--- a/guides/code/getting_started/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// This is a manifest file that'll be compiled into application.js, which will include all the files
-// listed below.
-//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
-//
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file.
-//
-// stub path allows dependency to be excluded from the asset bundle.
-//
-//= require jquery
-//= require jquery_ujs
-//= require turbolinks
-//= require_tree .
diff --git a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee b/guides/code/getting_started/app/assets/javascripts/comments.js.coffee
deleted file mode 100644
index 24f83d18bb..0000000000
--- a/guides/code/getting_started/app/assets/javascripts/comments.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
deleted file mode 100644
index 24f83d18bb..0000000000
--- a/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee b/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee
deleted file mode 100644
index 24f83d18bb..0000000000
--- a/guides/code/getting_started/app/assets/javascripts/welcome.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/guides/code/getting_started/app/assets/stylesheets/application.css b/guides/code/getting_started/app/assets/stylesheets/application.css
deleted file mode 100644
index 3192ec897b..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/application.css
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * This is a manifest file that'll be compiled into application.css, which will include all the files
- * listed below.
- *
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
- * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
- *
- * You're free to add application-wide styles to this file and they'll appear at the top of the
- * compiled file, but it's generally better to create a new file per style scope.
- *
- *= require_self
- *= require_tree .
- */
diff --git a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss b/guides/code/getting_started/app/assets/stylesheets/comments.css.scss
deleted file mode 100644
index e730912783..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/comments.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the Comments controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
deleted file mode 100644
index 1a7e15390c..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the posts controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss b/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss
deleted file mode 100644
index 77ce11a740..0000000000
--- a/guides/code/getting_started/app/assets/stylesheets/welcome.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the welcome controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/guides/code/getting_started/app/controllers/application_controller.rb b/guides/code/getting_started/app/controllers/application_controller.rb
deleted file mode 100644
index d83690e1b9..0000000000
--- a/guides/code/getting_started/app/controllers/application_controller.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class ApplicationController < ActionController::Base
- # Prevent CSRF attacks by raising an exception.
- # For APIs, you may want to use :null_session instead.
- protect_from_forgery with: :exception
-end
diff --git a/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb
deleted file mode 100644
index b2d9bcdf7f..0000000000
--- a/guides/code/getting_started/app/controllers/comments_controller.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-class CommentsController < ApplicationController
-
- http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
-
- def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.create(comment_params)
- redirect_to post_path(@post)
- end
-
- def destroy
- @post = Post.find(params[:post_id])
- @comment = @post.comments.find(params[:id])
- @comment.destroy
- redirect_to post_path(@post)
- end
-
- private
-
- def comment_params
- params.require(:comment).permit(:commenter, :body)
- end
-end
diff --git a/guides/code/getting_started/app/controllers/concerns/.keep b/guides/code/getting_started/app/controllers/concerns/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/app/controllers/concerns/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb
deleted file mode 100644
index 02689ad67b..0000000000
--- a/guides/code/getting_started/app/controllers/posts_controller.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-class PostsController < ApplicationController
-
- http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
-
- def index
- @posts = Post.all
- end
-
- def show
- @post = Post.find(params[:id])
- end
-
- def edit
- @post = Post.find(params[:id])
- end
-
- def update
- @post = Post.find(params[:id])
-
- if @post.update(post_params)
- redirect_to action: :show, id: @post.id
- else
- render 'edit'
- end
- end
-
- def new
- @post = Post.new
- end
-
- def create
- @post = Post.new(post_params)
-
- if @post.save
- redirect_to action: :show, id: @post.id
- else
- render 'new'
- end
- end
-
- def destroy
- @post = Post.find(params[:id])
- @post.destroy
-
- redirect_to action: :index
- end
-
- private
-
- def post_params
- params.require(:post).permit(:title, :text)
- end
-end
diff --git a/guides/code/getting_started/app/controllers/welcome_controller.rb b/guides/code/getting_started/app/controllers/welcome_controller.rb
deleted file mode 100644
index f9b859b9c9..0000000000
--- a/guides/code/getting_started/app/controllers/welcome_controller.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class WelcomeController < ApplicationController
- def index
- end
-end
diff --git a/guides/code/getting_started/app/helpers/application_helper.rb b/guides/code/getting_started/app/helpers/application_helper.rb
deleted file mode 100644
index de6be7945c..0000000000
--- a/guides/code/getting_started/app/helpers/application_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module ApplicationHelper
-end
diff --git a/guides/code/getting_started/app/helpers/comments_helper.rb b/guides/code/getting_started/app/helpers/comments_helper.rb
deleted file mode 100644
index 0ec9ca5f2d..0000000000
--- a/guides/code/getting_started/app/helpers/comments_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module CommentsHelper
-end
diff --git a/guides/code/getting_started/app/helpers/posts_helper.rb b/guides/code/getting_started/app/helpers/posts_helper.rb
deleted file mode 100644
index a7b8cec898..0000000000
--- a/guides/code/getting_started/app/helpers/posts_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module PostsHelper
-end
diff --git a/guides/code/getting_started/app/helpers/welcome_helper.rb b/guides/code/getting_started/app/helpers/welcome_helper.rb
deleted file mode 100644
index eeead45fc9..0000000000
--- a/guides/code/getting_started/app/helpers/welcome_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module WelcomeHelper
-end
diff --git a/guides/code/getting_started/app/mailers/.keep b/guides/code/getting_started/app/mailers/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/app/mailers/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/app/models/.keep b/guides/code/getting_started/app/models/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/app/models/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/app/models/comment.rb b/guides/code/getting_started/app/models/comment.rb
deleted file mode 100644
index 4e76c5b5b0..0000000000
--- a/guides/code/getting_started/app/models/comment.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Comment < ActiveRecord::Base
- belongs_to :post
-end
diff --git a/guides/code/getting_started/app/models/concerns/.keep b/guides/code/getting_started/app/models/concerns/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/app/models/concerns/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/post.rb
deleted file mode 100644
index 64e0d721fd..0000000000
--- a/guides/code/getting_started/app/models/post.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Post < ActiveRecord::Base
- has_many :comments, dependent: :destroy
-
- validates :title,
- presence: true,
- length: { minimum: 5 }
-end
diff --git a/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb
deleted file mode 100644
index 593493339e..0000000000
--- a/guides/code/getting_started/app/views/comments/_comment.html.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-<p>
- <strong>Commenter:</strong>
- <%= comment.commenter %>
-</p>
-
-<p>
- <strong>Comment:</strong>
- <%= comment.body %>
-</p>
-
-<p>
- <%= link_to 'Destroy Comment', [comment.post, comment],
- method: :delete,
- data: { confirm: 'Are you sure?' } %>
-</p>
diff --git a/guides/code/getting_started/app/views/comments/_form.html.erb b/guides/code/getting_started/app/views/comments/_form.html.erb
deleted file mode 100644
index 00cb3a08f0..0000000000
--- a/guides/code/getting_started/app/views/comments/_form.html.erb
+++ /dev/null
@@ -1,13 +0,0 @@
-<%= form_for([@post, @post.comments.build]) do |f| %>
- <p>
- <%= f.label :commenter %><br />
- <%= f.text_field :commenter %>
- </p>
- <p>
- <%= f.label :body %><br />
- <%= f.text_area :body %>
- </p>
- <p>
- <%= f.submit %>
- </p>
-<% end %>
diff --git a/guides/code/getting_started/app/views/layouts/application.html.erb b/guides/code/getting_started/app/views/layouts/application.html.erb
deleted file mode 100644
index d0ba8415e6..0000000000
--- a/guides/code/getting_started/app/views/layouts/application.html.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>Blog</title>
- <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
- <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
- <%= csrf_meta_tags %>
-</head>
-<body>
-
-<%= yield %>
-
-</body>
-</html>
diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb
deleted file mode 100644
index f2f83585e1..0000000000
--- a/guides/code/getting_started/app/views/posts/_form.html.erb
+++ /dev/null
@@ -1,27 +0,0 @@
-<%= form_for @post do |f| %>
- <% if @post.errors.any? %>
- <div id="error_explanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
- <p>
- <%= f.label :title %><br>
- <%= f.text_field :title %>
- </p>
-
- <p>
- <%= f.label :text %><br>
- <%= f.text_area :text %>
- </p>
-
- <p>
- <%= f.submit %>
- </p>
-<% end %>
-
diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/posts/edit.html.erb
deleted file mode 100644
index 393e7430d0..0000000000
--- a/guides/code/getting_started/app/views/posts/edit.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>Edit post</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Back', action: :index %>
diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb
deleted file mode 100644
index 7369f0396f..0000000000
--- a/guides/code/getting_started/app/views/posts/index.html.erb
+++ /dev/null
@@ -1,21 +0,0 @@
-<h1>Listing Posts</h1>
-<table>
- <tr>
- <th>Title</th>
- <th>Text</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
-
-<% @posts.each do |post| %>
- <tr>
- <td><%= post.title %></td>
- <td><%= post.text %></td>
- <td><%= link_to 'Show', action: :show, id: post.id %></td>
- <td><%= link_to 'Edit', action: :edit, id: post.id %></td>
- <td><%= link_to 'Destroy', { action: :destroy, id: post.id },
- method: :delete, data: { confirm: 'Are you sure?' } %></td>
- </tr>
-<% end %>
-</table>
diff --git a/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/posts/new.html.erb
deleted file mode 100644
index efa81038ec..0000000000
--- a/guides/code/getting_started/app/views/posts/new.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<h1>New post</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Back', action: :index %>
diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb
deleted file mode 100644
index e99e9edbb3..0000000000
--- a/guides/code/getting_started/app/views/posts/show.html.erb
+++ /dev/null
@@ -1,18 +0,0 @@
-<p>
- <strong>Title:</strong>
- <%= @post.title %>
-</p>
-
-<p>
- <strong>Text:</strong>
- <%= @post.text %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %>
diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb
deleted file mode 100644
index 56be8dd3cc..0000000000
--- a/guides/code/getting_started/app/views/welcome/index.html.erb
+++ /dev/null
@@ -1,4 +0,0 @@
-<h1>Hello, Rails!</h1>
-
-<%= link_to "My Blog", controller: "posts" %>
-<%= link_to "New Post", new_post_path %>
diff --git a/guides/code/getting_started/bin/bundle b/guides/code/getting_started/bin/bundle
deleted file mode 100755
index 45cf37fba4..0000000000
--- a/guides/code/getting_started/bin/bundle
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env ruby
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-require 'rubygems'
-load Gem.bin_path('bundler', 'bundle')
diff --git a/guides/code/getting_started/bin/rails b/guides/code/getting_started/bin/rails
deleted file mode 100755
index 728cd85aa5..0000000000
--- a/guides/code/getting_started/bin/rails
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env ruby
-APP_PATH = File.expand_path('../../config/application', __FILE__)
-require_relative '../config/boot'
-require 'rails/commands'
diff --git a/guides/code/getting_started/bin/rake b/guides/code/getting_started/bin/rake
deleted file mode 100755
index 17240489f6..0000000000
--- a/guides/code/getting_started/bin/rake
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env ruby
-require_relative '../config/boot'
-require 'rake'
-Rake.application.run
diff --git a/guides/code/getting_started/config.ru b/guides/code/getting_started/config.ru
deleted file mode 100644
index 5bc2a619e8..0000000000
--- a/guides/code/getting_started/config.ru
+++ /dev/null
@@ -1,4 +0,0 @@
-# This file is used by Rack-based servers to start the application.
-
-require ::File.expand_path('../config/environment', __FILE__)
-run Rails.application
diff --git a/guides/code/getting_started/config/application.rb b/guides/code/getting_started/config/application.rb
deleted file mode 100644
index 3d7604b659..0000000000
--- a/guides/code/getting_started/config/application.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require File.expand_path('../boot', __FILE__)
-
-require 'rails/all'
-
-# Require the gems listed in Gemfile, including any gems
-# you've limited to :test, :development, or :production.
-Bundler.require(:default, Rails.env)
-
-module Blog
- class Application < Rails::Application
- # Settings in config/environments/* take precedence over those specified here.
- # Application configuration should go into files in config/initializers
- # -- all .rb files in that directory are automatically loaded.
-
- # Custom directories with classes and modules you want to be autoloadable.
- # config.autoload_paths += %W(#{config.root}/extras)
- end
-end
diff --git a/guides/code/getting_started/config/boot.rb b/guides/code/getting_started/config/boot.rb
deleted file mode 100644
index 5e5f0c1fac..0000000000
--- a/guides/code/getting_started/config/boot.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# Set up gems listed in the Gemfile.
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-
-require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
diff --git a/guides/code/getting_started/config/database.yml b/guides/code/getting_started/config/database.yml
deleted file mode 100644
index 51a4dd459d..0000000000
--- a/guides/code/getting_started/config/database.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-# SQLite version 3.x
-# gem install sqlite3
-#
-# Ensure the SQLite 3 gem is defined in your Gemfile
-# gem 'sqlite3'
-development:
- adapter: sqlite3
- database: db/development.sqlite3
- pool: 5
- timeout: 5000
-
-# Warning: The database defined as "test" will be erased and
-# re-generated from your development database when you run "rake".
-# Do not set this db to the same as development or production.
-test:
- adapter: sqlite3
- database: db/test.sqlite3
- pool: 5
- timeout: 5000
-
-production:
- adapter: sqlite3
- database: db/production.sqlite3
- pool: 5
- timeout: 5000
diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb
deleted file mode 100644
index ee8d90dc65..0000000000
--- a/guides/code/getting_started/config/environment.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# Load the Rails application.
-require File.expand_path('../application', __FILE__)
-
-# Initialize the Rails application.
-Rails.application.initialize!
diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb
deleted file mode 100644
index ae9ffe209a..0000000000
--- a/guides/code/getting_started/config/environments/development.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-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
- # every request. This slows down response time but is perfect for development
- # since you don't have to restart the web server when you make code changes.
- config.cache_classes = false
-
- # Do not eager load code on boot.
- config.eager_load = false
-
- # Show full error reports and disable caching.
- config.consider_all_requests_local = true
- config.action_controller.perform_caching = false
-
- # Don't care if the mailer can't send.
- config.action_mailer.raise_delivery_errors = false
-
- # Print deprecation notices to the Rails logger.
- config.active_support.deprecation = :log
-
- # Only use best-standards-support built into browsers.
- config.action_dispatch.best_standards_support = :builtin
-
- # Raise an error on page load if there are pending migrations.
- config.active_record.migration_error = :page_load
-
- # Debug mode disables concatenation and preprocessing of assets.
- config.assets.debug = true
-end
diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb
deleted file mode 100644
index c8ae858574..0000000000
--- a/guides/code/getting_started/config/environments/production.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-Rails.application.configure do
- # Settings specified here will take precedence over those in config/application.rb.
-
- # Code is not reloaded between requests.
- config.cache_classes = true
-
- # Eager load code on boot. This eager loads most of Rails and
- # your application in memory, allowing both threaded web servers
- # and those relying on copy on write to perform better.
- # Rake tasks automatically ignore this option for performance.
- config.eager_load = true
-
- # Full error reports are disabled and caching is turned on.
- config.consider_all_requests_local = false
- config.action_controller.perform_caching = true
-
- # Enable Rack::Cache to put a simple HTTP cache in front of your application
- # Add `rack-cache` to your Gemfile before enabling this.
- # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
- # config.action_dispatch.rack_cache = true
-
- # Disable Rails's static asset server (Apache or nginx will already do this).
- config.serve_static_assets = false
-
- # Compress JavaScripts and CSS.
- config.assets.js_compressor = :uglifier
- # config.assets.css_compressor = :sass
-
- # Whether to fallback to assets pipeline if a precompiled asset is missed.
- config.assets.compile = false
-
- # 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'
-
- # Specifies the header that your server uses for sending files.
- # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
- # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
-
- # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
- # config.force_ssl = true
-
- # Set to :debug to see everything in the log.
- config.log_level = :info
-
- # Prepend all log lines with the following tags.
- # config.log_tags = [ :subdomain, :uuid ]
-
- # Use a different logger for distributed setups.
- # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
-
- # Use a different cache store in production.
- # config.cache_store = :mem_cache_store
-
- # Enable serving of images, stylesheets, and JavaScripts from an asset server.
- # config.action_controller.asset_host = "http://assets.example.com"
-
- # 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 )
-
- # Ignore bad email addresses and do not raise email delivery errors.
- # Set this to true and configure the email server for immediate delivery to raise delivery errors.
- # config.action_mailer.raise_delivery_errors = false
-
- # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation cannot be found).
- config.i18n.fallbacks = true
-
- # Send deprecation notices to registered listeners.
- config.active_support.deprecation = :notify
-
- # Disable automatic flushing of the log to improve performance.
- # config.autoflush_log = false
-
- # Use default logging formatter so that PID and timestamp are not suppressed.
- config.log_formatter = ::Logger::Formatter.new
-end
diff --git a/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb
deleted file mode 100644
index 680d0b9e06..0000000000
--- a/guides/code/getting_started/config/environments/test.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-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
- # test suite. You never need to work with it otherwise. Remember that
- # your test database is "scratch space" for the test suite and is wiped
- # and recreated between test runs. Don't rely on the data there!
- config.cache_classes = true
-
- # Do not eager load code on boot. This avoids loading your whole application
- # just for the purpose of running a single test. If you are using a tool that
- # preloads Rails for running tests, you may have to set it to true.
- config.eager_load = false
-
- # Configure static asset server for tests with Cache-Control for performance.
- config.serve_static_assets = true
- config.static_cache_control = 'public, max-age=3600'
-
- # Show full error reports and disable caching.
- config.consider_all_requests_local = true
- config.action_controller.perform_caching = false
-
- # Raise exceptions instead of rendering exception templates.
- config.action_dispatch.show_exceptions = false
-
- # Disable request forgery protection in test environment.
- config.action_controller.allow_forgery_protection = false
-
- # Tell Action Mailer not to deliver emails to the real world.
- # The :test delivery method accumulates sent emails in the
- # ActionMailer::Base.deliveries array.
- config.action_mailer.delivery_method = :test
-
- # Print deprecation notices to the stderr.
- config.active_support.deprecation = :stderr
-end
diff --git a/guides/code/getting_started/config/initializers/backtrace_silencers.rb b/guides/code/getting_started/config/initializers/backtrace_silencers.rb
deleted file mode 100644
index 59385cdf37..0000000000
--- a/guides/code/getting_started/config/initializers/backtrace_silencers.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
-# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
-
-# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
-# Rails.backtrace_cleaner.remove_silencers!
diff --git a/guides/code/getting_started/config/initializers/filter_parameter_logging.rb b/guides/code/getting_started/config/initializers/filter_parameter_logging.rb
deleted file mode 100644
index 4a994e1e7b..0000000000
--- a/guides/code/getting_started/config/initializers/filter_parameter_logging.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Configure sensitive parameters which will be filtered from the log file.
-Rails.application.config.filter_parameters += [:password]
diff --git a/guides/code/getting_started/config/initializers/inflections.rb b/guides/code/getting_started/config/initializers/inflections.rb
deleted file mode 100644
index ac033bf9dc..0000000000
--- a/guides/code/getting_started/config/initializers/inflections.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Add new inflection rules using the following format. Inflections
-# are locale specific, and you may define rules for as many different
-# locales as you wish. All of these examples are active by default:
-# ActiveSupport::Inflector.inflections(:en) do |inflect|
-# inflect.plural /^(ox)$/i, '\1en'
-# inflect.singular /^(ox)en/i, '\1'
-# inflect.irregular 'person', 'people'
-# inflect.uncountable %w( fish sheep )
-# end
-
-# These inflection rules are supported but not enabled by default:
-# ActiveSupport::Inflector.inflections(:en) do |inflect|
-# inflect.acronym 'RESTful'
-# end
diff --git a/guides/code/getting_started/config/initializers/locale.rb b/guides/code/getting_started/config/initializers/locale.rb
deleted file mode 100644
index d89dac7c6a..0000000000
--- a/guides/code/getting_started/config/initializers/locale.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
-# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
-# Rails.application.config.time_zone = 'Central Time (US & Canada)'
-
-# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
-# Rails.application.config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
-# Rails.application.config.i18n.default_locale = :de
diff --git a/guides/code/getting_started/config/initializers/mime_types.rb b/guides/code/getting_started/config/initializers/mime_types.rb
deleted file mode 100644
index 72aca7e441..0000000000
--- a/guides/code/getting_started/config/initializers/mime_types.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# 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/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb
deleted file mode 100644
index c2a549c299..0000000000
--- a/guides/code/getting_started/config/initializers/secret_token.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# Your secret key for verifying the integrity of signed cookies.
-# If you change this key, all old signed cookies will become invalid!
-
-# Make sure the secret is at least 30 characters and all random,
-# no regular words or you'll be exposed to dictionary attacks.
-# You can use `rake secret` to generate a secure secret key.
-
-# Make sure your secret_key_base is kept private
-# if you're sharing your code publicly.
-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
deleted file mode 100644
index 1b9fa324d4..0000000000
--- a/guides/code/getting_started/config/initializers/session_store.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-Rails.application.config.session_store :cookie_store, key: '_blog_session'
diff --git a/guides/code/getting_started/config/initializers/wrap_parameters.rb b/guides/code/getting_started/config/initializers/wrap_parameters.rb
deleted file mode 100644
index 33725e95fd..0000000000
--- a/guides/code/getting_started/config/initializers/wrap_parameters.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# Be sure to restart your server when you modify this file.
-
-# This file contains settings for ActionController::ParamsWrapper which
-# is enabled by default.
-
-# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
-ActiveSupport.on_load(:action_controller) do
- wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
-end
-
-# To enable root element in JSON for ActiveRecord objects.
-# ActiveSupport.on_load(:active_record) do
-# self.include_root_in_json = true
-# end
diff --git a/guides/code/getting_started/config/locales/en.yml b/guides/code/getting_started/config/locales/en.yml
deleted file mode 100644
index 0653957166..0000000000
--- a/guides/code/getting_started/config/locales/en.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-# Files in the config/locales directory are used for internationalization
-# and are automatically loaded by Rails. If you want to use locales other
-# than English, add the necessary files in this directory.
-#
-# To use the locales, use `I18n.t`:
-#
-# I18n.t 'hello'
-#
-# In views, this is aliased to just `t`:
-#
-# <%= t('hello') %>
-#
-# To use a different locale, set it with `I18n.locale`:
-#
-# I18n.locale = :es
-#
-# This would use the information in config/locales/es.yml.
-#
-# To learn more, please read the Rails Internationalization guide
-# available at http://guides.rubyonrails.org/i18n.html.
-
-en:
- hello: "Hello world"
diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb
deleted file mode 100644
index 65d273b58d..0000000000
--- a/guides/code/getting_started/config/routes.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-Rails.application.routes.draw do
- resources :posts do
- resources :comments
- end
-
- root "welcome#index"
-end
diff --git a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb b/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb
deleted file mode 100644
index 602bef31ab..0000000000
--- a/guides/code/getting_started/db/migrate/20130122042648_create_posts.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class CreatePosts < ActiveRecord::Migration
- def change
- create_table :posts do |t|
- t.string :title
- t.text :text
-
- t.timestamps
- end
- end
-end
diff --git a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb b/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb
deleted file mode 100644
index 3e51f9c0f7..0000000000
--- a/guides/code/getting_started/db/migrate/20130122045842_create_comments.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateComments < ActiveRecord::Migration
- def change
- create_table :comments do |t|
- t.string :commenter
- t.text :body
- t.references :post, index: true
-
- t.timestamps
- end
- end
-end
diff --git a/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb
deleted file mode 100644
index 101fe712a1..0000000000
--- a/guides/code/getting_started/db/schema.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# encoding: UTF-8
-# This file is auto-generated from the current state of the database. Instead
-# of editing this file, please use the migrations feature of Active Record to
-# incrementally modify your database, and then regenerate this schema definition.
-#
-# Note that this schema.rb definition is the authoritative source for your
-# database schema. If you need to create the application database on another
-# system, you should be using db:schema:load, not running all the migrations
-# from scratch. The latter is a flawed and unsustainable approach (the more migrations
-# you'll amass, the slower it'll run and the greater likelihood for issues).
-#
-# It's strongly recommended that you check this file into your version control system.
-
-ActiveRecord::Schema.define(version: 20130122045842) do
-
- create_table "comments", force: true do |t|
- t.string "commenter"
- t.text "body"
- t.integer "post_id"
- t.datetime "created_at"
- t.datetime "updated_at"
- end
-
- add_index "comments", ["post_id"], name: "index_comments_on_post_id"
-
- create_table "posts", force: true do |t|
- t.string "title"
- t.text "text"
- t.datetime "created_at"
- t.datetime "updated_at"
- end
-
-end
diff --git a/guides/code/getting_started/db/seeds.rb b/guides/code/getting_started/db/seeds.rb
deleted file mode 100644
index 4edb1e857e..0000000000
--- a/guides/code/getting_started/db/seeds.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file should contain all the record creation needed to seed the database with its default values.
-# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
-#
-# Examples:
-#
-# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
-# Mayor.create(name: 'Emanuel', city: cities.first)
diff --git a/guides/code/getting_started/lib/assets/.keep b/guides/code/getting_started/lib/assets/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/lib/assets/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/lib/tasks/.keep b/guides/code/getting_started/lib/tasks/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/lib/tasks/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/log/.keep b/guides/code/getting_started/log/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/log/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html
deleted file mode 100644
index 3265cc8e33..0000000000
--- a/guides/code/getting_started/public/404.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>The page you were looking for doesn't exist (404)</title>
- <style>
- body {
- background-color: #EFEFEF;
- color: #2E2F30;
- text-align: center;
- font-family: arial, sans-serif;
- }
-
- div.dialog {
- width: 25em;
- margin: 4em auto 0 auto;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #BBB;
- border-top: #B00100 solid 4px;
- border-top-left-radius: 9px;
- border-top-right-radius: 9px;
- background-color: white;
- padding: 7px 4em 0 4em;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
-
- h1 {
- font-size: 100%;
- color: #730E15;
- line-height: 1.5em;
- }
-
- body > p {
- width: 33em;
- margin: 0 auto 1em;
- padding: 1em 0;
- background-color: #F7F7F7;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #999;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- border-top-color: #DADADA;
- color: #666;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
- </style>
-</head>
-
-<body>
- <!-- This file lives in public/404.html -->
- <div class="dialog">
- <h1>The page you were looking for doesn't exist.</h1>
- <p>You may have mistyped the address or the page may have moved.</p>
- </div>
- <p>If you are the application owner check the logs for more information.</p>
-</body>
-</html>
diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html
deleted file mode 100644
index d823a8fc77..0000000000
--- a/guides/code/getting_started/public/422.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>The change you wanted was rejected (422)</title>
- <style>
- body {
- background-color: #EFEFEF;
- color: #2E2F30;
- text-align: center;
- font-family: arial, sans-serif;
- }
-
- div.dialog {
- width: 25em;
- margin: 4em auto 0 auto;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #BBB;
- border-top: #B00100 solid 4px;
- border-top-left-radius: 9px;
- border-top-right-radius: 9px;
- background-color: white;
- padding: 7px 4em 0 4em;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
-
- h1 {
- font-size: 100%;
- color: #730E15;
- line-height: 1.5em;
- }
-
- body > p {
- width: 33em;
- margin: 0 auto 1em;
- padding: 1em 0;
- background-color: #F7F7F7;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #999;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- border-top-color: #DADADA;
- color: #666;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
- </style>
-</head>
-
-<body>
- <!-- This file lives in public/422.html -->
- <div class="dialog">
- <h1>The change you wanted was rejected.</h1>
- <p>Maybe you tried to change something you didn't have access to.</p>
- </div>
- <p>If you are the application owner check the logs for more information.</p>
-</body>
-</html>
diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html
deleted file mode 100644
index ebf6d4c00c..0000000000
--- a/guides/code/getting_started/public/500.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>We're sorry, but something went wrong (500)</title>
- <style>
- body {
- background-color: #EFEFEF;
- color: #2E2F30;
- text-align: center;
- font-family: arial, sans-serif;
- }
-
- div.dialog {
- width: 25em;
- margin: 4em auto 0 auto;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #BBB;
- border-top: #B00100 solid 4px;
- border-top-left-radius: 9px;
- border-top-right-radius: 9px;
- background-color: white;
- padding: 7px 4em 0 4em;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
-
- h1 {
- font-size: 100%;
- color: #730E15;
- line-height: 1.5em;
- }
-
- body > p {
- width: 33em;
- margin: 0 auto 1em;
- padding: 1em 0;
- background-color: #F7F7F7;
- border: 1px solid #CCC;
- border-right-color: #999;
- border-left-color: #999;
- border-bottom-color: #999;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- border-top-color: #DADADA;
- color: #666;
- box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
- }
- </style>
-</head>
-
-<body>
- <!-- This file lives in public/500.html -->
- <div class="dialog">
- <h1>We're sorry, but something went wrong.</h1>
- </div>
- <p>If you are the application owner check the logs for more information.</p>
-</body>
-</html>
diff --git a/guides/code/getting_started/public/favicon.ico b/guides/code/getting_started/public/favicon.ico
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/public/favicon.ico
+++ /dev/null
diff --git a/guides/code/getting_started/public/robots.txt b/guides/code/getting_started/public/robots.txt
deleted file mode 100644
index 3c9c7c01f3..0000000000
--- a/guides/code/getting_started/public/robots.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
-#
-# To ban all spiders from the entire site uncomment the next two lines:
-# User-agent: *
-# Disallow: /
diff --git a/guides/code/getting_started/test/controllers/.keep b/guides/code/getting_started/test/controllers/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/test/controllers/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/test/controllers/comments_controller_test.rb b/guides/code/getting_started/test/controllers/comments_controller_test.rb
deleted file mode 100644
index 2ec71b4ec5..0000000000
--- a/guides/code/getting_started/test/controllers/comments_controller_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class CommentsControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/guides/code/getting_started/test/controllers/posts_controller_test.rb b/guides/code/getting_started/test/controllers/posts_controller_test.rb
deleted file mode 100644
index 7a6ee4f1db..0000000000
--- a/guides/code/getting_started/test/controllers/posts_controller_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class PostsControllerTest < ActionController::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/guides/code/getting_started/test/controllers/welcome_controller_test.rb b/guides/code/getting_started/test/controllers/welcome_controller_test.rb
deleted file mode 100644
index dff8e9d2c5..0000000000
--- a/guides/code/getting_started/test/controllers/welcome_controller_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'test_helper'
-
-class WelcomeControllerTest < ActionController::TestCase
- test "should get index" do
- get :index
- assert_response :success
- end
-
-end
diff --git a/guides/code/getting_started/test/fixtures/.keep b/guides/code/getting_started/test/fixtures/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/test/fixtures/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml
deleted file mode 100644
index 9e409d8a61..0000000000
--- a/guides/code/getting_started/test/fixtures/comments.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-one:
- commenter: MyString
- body: MyText
- post_id:
-
-two:
- commenter: MyString
- body: MyText
- post_id:
diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml
deleted file mode 100644
index 46b01c3bb4..0000000000
--- a/guides/code/getting_started/test/fixtures/posts.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-
-one:
- title: MyString
- text: MyText
-
-two:
- title: MyString
- text: MyText
diff --git a/guides/code/getting_started/test/helpers/.keep b/guides/code/getting_started/test/helpers/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/test/helpers/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/test/helpers/comments_helper_test.rb b/guides/code/getting_started/test/helpers/comments_helper_test.rb
deleted file mode 100644
index 2518c16bd5..0000000000
--- a/guides/code/getting_started/test/helpers/comments_helper_test.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'test_helper'
-
-class CommentsHelperTest < ActionView::TestCase
-end
diff --git a/guides/code/getting_started/test/helpers/posts_helper_test.rb b/guides/code/getting_started/test/helpers/posts_helper_test.rb
deleted file mode 100644
index 48549c2ea1..0000000000
--- a/guides/code/getting_started/test/helpers/posts_helper_test.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'test_helper'
-
-class PostsHelperTest < ActionView::TestCase
-end
diff --git a/guides/code/getting_started/test/helpers/welcome_helper_test.rb b/guides/code/getting_started/test/helpers/welcome_helper_test.rb
deleted file mode 100644
index d6ded5995f..0000000000
--- a/guides/code/getting_started/test/helpers/welcome_helper_test.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-require 'test_helper'
-
-class WelcomeHelperTest < ActionView::TestCase
-end
diff --git a/guides/code/getting_started/test/integration/.keep b/guides/code/getting_started/test/integration/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/test/integration/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/test/mailers/.keep b/guides/code/getting_started/test/mailers/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/test/mailers/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/test/models/.keep b/guides/code/getting_started/test/models/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/test/models/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/test/models/comment_test.rb b/guides/code/getting_started/test/models/comment_test.rb
deleted file mode 100644
index b6d6131a96..0000000000
--- a/guides/code/getting_started/test/models/comment_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class CommentTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/guides/code/getting_started/test/models/post_test.rb b/guides/code/getting_started/test/models/post_test.rb
deleted file mode 100644
index 6d9d463a71..0000000000
--- a/guides/code/getting_started/test/models/post_test.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-require 'test_helper'
-
-class PostTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
-end
diff --git a/guides/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb
deleted file mode 100644
index f91a4375dc..0000000000
--- a/guides/code/getting_started/test/test_helper.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-ENV["RAILS_ENV"] = "test"
-require File.expand_path('../../config/environment', __FILE__)
-require 'rails/test_help'
-
-class ActiveSupport::TestCase
- ActiveRecord::Migration.check_pending!
-
- # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
- #
- # Note: You'll currently still have to declare fixtures explicitly in integration tests
- # -- they do not yet inherit this setting
- fixtures :all
-
- # Add more helper methods to be used by all tests here...
-end
diff --git a/guides/code/getting_started/vendor/assets/javascripts/.keep b/guides/code/getting_started/vendor/assets/javascripts/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/vendor/assets/javascripts/.keep
+++ /dev/null
diff --git a/guides/code/getting_started/vendor/assets/stylesheets/.keep b/guides/code/getting_started/vendor/assets/stylesheets/.keep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/guides/code/getting_started/vendor/assets/stylesheets/.keep
+++ /dev/null
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..1ea18ba9f5 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -47,8 +47,7 @@ module RailsGuides
end
def dom_id_text(text)
- text.downcase.gsub(/\?/, '-questionmark').gsub(/!/, '-bang').gsub(/[^a-z0-9]+/, ' ')
- .strip.gsub(/\s+/, '-')
+ text.downcase.gsub(/\?/, '-questionmark').gsub(/!/, '-bang').gsub(/\s+/, '-')
end
def engine
@@ -79,10 +78,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 +115,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 +129,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..52eeb4c2bc 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -8,7 +8,7 @@ Rails 2.3 delivers a variety of new and improved features, including pervasive R
Application Architecture
------------------------
-There are two major changes in the architecture of Rails applications: complete integration of the [Rack](http://rack.rubyforge.org/) modular web server interface, and renewed support for Rails Engines.
+There are two major changes in the architecture of Rails applications: complete integration of the [Rack](http://rack.github.io/) modular web server interface, and renewed support for Rails Engines.
### Rack Integration
@@ -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 2d4be0cda7..aec3a383d6 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -308,7 +308,7 @@ More Information:
Major re-write was done in the Action View helpers, implementing Unobtrusive JavaScript (UJS) hooks and removing the old inline AJAX commands. This enables Rails to use any compliant UJS driver to implement the UJS hooks in the helpers.
-What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper.) To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example:
+What this means is that all previous `remote_<method>` helpers have been removed from Rails core and put into the [Prototype Legacy Helper](http://github.com/rails/prototype_legacy_helper). To get UJS hooks into your HTML, you now pass `:remote => true` instead. For example:
```ruby
form_for @post, :remote => true
@@ -521,7 +521,7 @@ A large effort was made in Active Support to make it cherry pickable, that is, y
These are the main changes in Active Support:
* Large clean up of the library removing unused methods throughout.
-* Active Support no longer provides vendored versions of [TZInfo](http://tzinfo.rubyforge.org/), [Memcache Client](http://deveiate.org/projects/RMemCache/) and [Builder](http://builder.rubyforge.org/,) these are all included as dependencies and installed via the `bundle install` command.
+* Active Support no longer provides vendored versions of TZInfo, Memcache Client and Builder. These are all included as dependencies and installed via the `bundle install` command.
* Safe buffers are implemented in `ActiveSupport::SafeBuffer`.
* Added `Array.uniq_by` and `Array.uniq_by!`.
* Removed `Array#rand` and backported `Array#sample` from Ruby 1.9.
@@ -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_1_release_notes.md b/guides/source/3_1_release_notes.md
index 485f8c756b..7626296e7d 100644
--- a/guides/source/3_1_release_notes.md
+++ b/guides/source/3_1_release_notes.md
@@ -173,7 +173,7 @@ The assets pipeline is powered by [Sprockets](https://github.com/sstephenson/spr
### HTTP Streaming
-HTTP Streaming is another change that is new in Rails 3.1. This lets the browser download your stylesheets and JavaScript files while the server is still generating the response. This requires Ruby 1.9.2, is opt-in and requires support from the web server as well, but the popular combo of nginx and unicorn is ready to take advantage of it.
+HTTP Streaming is another change that is new in Rails 3.1. This lets the browser download your stylesheets and JavaScript files while the server is still generating the response. This requires Ruby 1.9.2, is opt-in and requires support from the web server as well, but the popular combo of NGINX and Unicorn is ready to take advantage of it.
### Default JS library is now jQuery
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index cdcde67869..2416e1a228 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -562,4 +562,4 @@ Credits
See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them.
-Rails 3.2 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev.)
+Rails 3.2 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev).
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
new file mode 100644
index 0000000000..be007f93a7
--- /dev/null
+++ b/guides/source/4_2_release_notes.md
@@ -0,0 +1,243 @@
+Ruby on Rails 4.2 Release Notes
+===============================
+
+Highlights in Rails 4.2:
+
+These release notes cover only the major changes. To know about various bug
+fixes and changes, please refer to the change logs or check out the
+[list of commits](https://github.com/rails/rails/commits/master) in the main
+Rails repository on GitHub.
+
+--------------------------------------------------------------------------------
+
+Upgrading to Rails 4.2
+----------------------
+
+If you're upgrading an existing application, it's a great idea to have good test
+coverage before going in. You should also first upgrade to Rails 4.1 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 4.2. A list of things to watch out for when upgrading is
+available in the
+[Upgrading Ruby on Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-4-1-to-rails-4-2)
+guide.
+
+
+Major Features
+--------------
+
+
+
+Railties
+--------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md)
+for detailed changes.
+
+### Removals
+
+* The `rails application` command has been removed without replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/11616))
+
+### Notable changes
+
+* Introduced `bin/setup` script to bootstrap an application.
+ ([Pull Request](https://github.com/rails/rails/pull/15189))
+
+* Changed default value for `config.assets.digest` to `true` in development.
+ ([Pull Request](https://github.com/rails/rails/pull/15155))
+
+* Introduced an API to register new extensions for `rake notes`.
+ ([Pull Request](https://github.com/rails/rails/pull/14379))
+
+* Introduced `Rails.gem_version` as a convenience method to return `Gem::Version.new(Rails.version)`.
+ ([Pull Request](https://github.com/rails/rails/pull/14101))
+
+
+Action Pack
+-----------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-2-stable/actionpack/CHANGELOG.md)
+for detailed changes.
+
+### Deprecations
+
+* Deprecated support for setting the `to:` option of a router to a symbol or a
+ string that does not contain a `#` character:
+
+ get '/posts', to: MyRackApp => (No change necessary)
+ get '/posts', to: 'post#index' => (No change necessary)
+ get '/posts', to: 'posts' => get '/posts', controller: :posts
+ get '/posts', to: :index => get '/posts', action: :index
+
+ ([Commit](https://github.com/rails/rails/commit/cc26b6b7bccf0eea2e2c1a9ebdcc9d30ca7390d9))
+
+### Notable changes
+
+* The `*_filter` family methods has been removed from the documentation. Their
+ usage are discouraged in favor of the `*_action` family methods:
+
+ after_filter => after_action
+ append_after_filter => append_after_action
+ append_around_filter => append_around_action
+ append_before_filter => append_before_action
+ around_filter => around_action
+ before_filter => before_action
+ prepend_after_filter => prepend_after_action
+ prepend_around_filter => prepend_around_action
+ prepend_before_filter => prepend_before_action
+ skip_after_filter => skip_after_action
+ skip_around_filter => skip_around_action
+ skip_before_filter => skip_before_action
+ skip_filter => skip_action_callback
+
+ If your application is depending on these methods, you should use the
+ replacement `*_action` methods instead. These methods will be deprecated in
+ the future and eventually removed from Rails.
+ (Commit [1](https://github.com/rails/rails/commit/6c5f43bab8206747a8591435b2aa0ff7051ad3de),
+ [2](https://github.com/rails/rails/commit/489a8f2a44dc9cea09154ee1ee2557d1f037c7d4))
+
+* Added HTTP method `MKCALENDAR` from RFC-4791
+ ([Pull Request](https://github.com/rails/rails/pull/15121))
+
+* `*_fragment.action_controller` notifications now include the controller and action name
+ in the payload.
+ ([Pull Request](https://github.com/rails/rails/pull/14137))
+
+* Segments that are passed into URL helpers are now automatically escaped.
+ ([Commit](https://github.com/rails/rails/commit/5460591f0226a9d248b7b4f89186bd5553e7768f))
+
+* Improved Routing Error page with fuzzy matching for route search.
+ ([Pull Request](https://github.com/rails/rails/pull/14619))
+
+* Added option to disable logging of CSRF failures.
+ ([Pull Request](https://github.com/rails/rails/pull/14280))
+
+
+Action Mailer
+-------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-2-stable/actionmailer/CHANGELOG.md)
+for detailed changes.
+
+### Notable changes
+
+
+Active Record
+-------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md)
+for detailed changes.
+
+### Deprecations
+
+* Deprecated using `.joins`, `.preload` and `.eager_load` with associations that
+ depends on the instance state (i.e. those defined with a scope that takes an
+ argument) without replacement.
+ ([Commit](https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1))
+
+* Deprecated passing Active Record objects to `.find` or `.exists?`. Call `#id`
+ on the objects first.
+ (Commit [1](https://github.com/rails/rails/commit/d92ae6ccca3bcfd73546d612efaea011270bd270),
+ [2](https://github.com/rails/rails/commit/d35f0033c7dec2b8d8b52058fb8db495d49596f7))
+
+* Deprecated half-baked support for PostgreSQL range values with excluding
+ beginnings. We currently map PostgreSQL ranges to Ruby ranges. This conversion
+ is not fully possible because the Ruby range does not support excluded
+ beginnings.
+
+ The current solution of incrementing the beginning is not correct and is now
+ deprecated. For subtypes where we don't know how to increment (e.g. `#succ`
+ is not defined) it will raise an `ArgumentError` for ranges with excluding
+ beginnings.
+
+ ([Commit](https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3))
+
+### Notable changes
+
+* Added support for `#pretty_print` in `ActiveRecord::Base` objects.
+ ([Pull Request](https://github.com/rails/rails/pull/15172))
+
+* PostgreSQL and SQLite adapters no longer add a default limit of 255 characters
+ on string columns.
+ ([Pull Request](https://github.com/rails/rails/pull/14579))
+
+* `sqlite3:///some/path` now resolves to the absolute system path `/some/path`.
+ For relative paths, use `sqlite3:some/path` instead. (Previously, `sqlite3:///some/path`
+ resolved to the relative path `some/path`. This behaviour was deprecated on
+ Rails 4.1.)
+ ([Pull Request](https://github.com/rails/rails/pull/14569))
+
+* Introduced `#validate` as an alias for `#valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
+
+* `#touch` now accepts multiple attributes to be touched at once.
+ ([Pull Request](https://github.com/rails/rails/pull/14423))
+
+* Added support for fractional seconds for MySQL 5.6 and above.
+ (Pull Request [1](https://github.com/rails/rails/pull/8240), [2](https://github.com/rails/rails/pull/14359))
+
+* Added support for the `citext` column type in PostgreSQL adapter.
+ ([Pull Request](https://github.com/rails/rails/pull/12523))
+
+* Added support for user-created range types in PostgreSQL adapter.
+ ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032))
+
+Active Model
+------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md)
+for detailed changes.
+
+### Notable changes
+
+* Introduced `#validate` as an alias for `#valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
+
+
+Active Support
+--------------
+
+Please refer to the
+[Changelog](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md)
+for detailed changes.
+
+### Removals
+
+* Removed deprecated `Numeric#ago`, `Numeric#until`, `Numeric#since`,
+ `Numeric#from_now`. ([Commit](https://github.com/rails/rails/commit/f1eddea1e3f6faf93581c43651348f48b2b7d8bb))
+
+* Removed deprecated string based terminators for `ActiveSupport::Callbacks`.
+ ([Pull Request](https://github.com/rails/rails/pull/15100))
+
+### Deprecations
+
+* Deprecated `Class#superclass_delegating_accessor`, use `Class#class_attribute`
+ instead. ([Pull Request](https://github.com/rails/rails/pull/14271))
+
+* Deprecated `ActiveSupport::SafeBuffer#prepend!` as `ActiveSupport::SafeBuffer#prepend`
+ now performs the same function. ([Pull Request](https://github.com/rails/rails/pull/14529))
+
+### Notable changes
+
+* The `humanize` inflector helper now strips any leading underscores.
+ ([Commit](https://github.com/rails/rails/commit/daaa21bc7d20f2e4ff451637423a25ff2d5e75c7))
+
+* Added `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5`.
+ ([Pull Request](https://github.com/rails/rails/pull/12016))
+
+* Introduce `Concern#class_methods` as an alternative to `module ClassMethods`,
+ as well as `Kernel#concern` to avoid the `module Foo; extend ActiveSupport::Concern; end`
+ boilerplate. ([Commit](https://github.com/rails/rails/commit/b16c36e688970df2f96f793a759365b248b582ad))
+
+Credits
+-------
+
+See the
+[full list of contributors to Rails](http://contributors.rubyonrails.org/) for
+the many people who spent many hours making Rails, the stable and robust
+framework it is. Kudos to all of them.
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 7e39f761f2..6ec3aa78a4 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -15,11 +15,5 @@
</p>
<% end %>
<p>
- The guides for Rails 4.0.x are available at <a href="http://guides.rubyonrails.org/v4.0.4/">http://guides.rubyonrails.org/v4.0.4/</a>.
-</p>
-<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 1735188f27..3d15319ca4 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1078,7 +1078,7 @@ Rails keeps a log file for each environment in the `log` folder. These are extre
### Parameters Filtering
-You can filter certain request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log.
+You can filter out sensitive request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log.
```ruby
config.filter_parameters << :password
@@ -1086,7 +1086,7 @@ config.filter_parameters << :password
### Redirects Filtering
-Sometimes it's desirable to filter out from log files some sensible locations your application is redirecting to.
+Sometimes it's desirable to filter out from log files some sensitive locations your application is redirecting to.
You can do that by using the `config.filter_redirect` configuration option:
```ruby
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 6dc7fb1606..cb1c1c653d 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -17,7 +17,10 @@ After reading this guide, you will know:
Introduction
------------
-Action Mailer allows you to send emails from your application using mailer classes and views. Mailers work very similarly to controllers. They inherit from `ActionMailer::Base` and live in `app/mailers`, and they have associated views that appear in `app/views`.
+Action Mailer allows you to send emails from your application using mailer classes
+and views. Mailers work very similarly to controllers. They inherit from
+`ActionMailer::Base` and live in `app/mailers`, and they have associated views
+that appear in `app/views`.
Sending Emails
--------------
@@ -30,7 +33,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
@@ -84,8 +87,11 @@ Here is a quick explanation of the items presented in the preceding method. For
a full list of all available options, please have a look further down at the
Complete List of Action Mailer user-settable attributes section.
-* `default Hash` - This is a hash of default values for any email you send from this mailer. In this case we are setting the `:from` header to a value for all messages in this class. This can be overridden on a per-email basis.
-* `mail` - The actual email message, we are passing the `:to` and `:subject` headers in.
+* `default Hash` - This is a hash of default values for any email you send from
+this mailer. In this case we are setting the `:from` header to a value for all
+messages in this class. This can be overridden on a per-email basis.
+* `mail` - The actual email message, we are passing the `:to` and `:subject`
+headers in.
Just like controllers, any instance variables we define in the method become
available for use in the views.
@@ -146,12 +152,12 @@ 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
-`app/controllers/users_controller.rb` make it instruct the UserMailer to deliver
+`app/controllers/users_controller.rb` make it instruct the `UserMailer` to deliver
an email to the newly created user by editing the create action and inserting a
call to `UserMailer.welcome_email` right after the user is successfully saved:
@@ -230,9 +236,11 @@ different, encode your content and pass in the encoded content and encoding in a
```ruby
encoded_content = SpecialEncode(File.read('/path/to/filename.jpg'))
- attachments['filename.jpg'] = {mime_type: 'application/x-gzip',
- encoding: 'SpecialEncoding',
- content: encoded_content }
+ attachments['filename.jpg'] = {
+ mime_type: 'application/x-gzip',
+ encoding: 'SpecialEncoding',
+ content: encoded_content
+ }
```
NOTE: If you specify an encoding, Mail will assume that your content is already
@@ -301,7 +309,7 @@ email address in the format `"Full Name <email>"`.
```ruby
def welcome_email(user)
@user = user
- email_with_name = "#{@user.name} <#{@user.email}>"
+ email_with_name = %("#{@user.name}" <#{@user.email}>)
mail(to: email_with_name, subject: 'Welcome to My Awesome Site')
end
```
@@ -608,7 +616,7 @@ files (environment.rb, production.rb, etc...)
| Configuration | Description |
|---------------|-------------|
|`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.|
-|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>|
+|`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>|
|`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>|
|`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.|
|`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.|
@@ -617,7 +625,7 @@ files (environment.rb, production.rb, etc...)
|`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).|
For a complete writeup of possible configurations see the
-[Action Mailer section](configuring.html#configuring-action-mailer) in
+[Configuring Action Mailer](configuring.html#configuring-action-mailer) in
our Configuring Rails Applications guide.
### Example Action Mailer Configuration
@@ -662,6 +670,7 @@ You can find detailed instructions on how to test your mailers in the
Intercepting Emails
-------------------
+
There are situations where you need to edit an email before it's
delivered. Fortunately Action Mailer provides hooks to intercept every
email. You can register an interceptor to make modifications to mail messages
@@ -685,5 +694,5 @@ ActionMailer::Base.register_interceptor(SandboxEmailInterceptor) if Rails.env.st
NOTE: The example above uses a custom environment called "staging" for a
production like server but for testing purposes. You can read
-[Creating Rails environments](./configuring.html#creating-rails-environments)
+[Creating Rails environments](configuring.html#creating-rails-environments)
for more information about custom Rails environments.
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 74f95bfcfd..ef7ef5a50e 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -28,22 +28,22 @@ 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 article
[...]
invoke scaffold_controller
- create app/controllers/posts_controller.rb
+ create app/controllers/articles_controller.rb
invoke erb
- create app/views/posts
- create app/views/posts/index.html.erb
- create app/views/posts/edit.html.erb
- create app/views/posts/show.html.erb
- create app/views/posts/new.html.erb
- create app/views/posts/_form.html.erb
+ create app/views/articles
+ create app/views/articles/index.html.erb
+ create app/views/articles/edit.html.erb
+ create app/views/articles/show.html.erb
+ create app/views/articles/new.html.erb
+ create app/views/articles/_form.html.erb
[...]
```
There is a naming convention for views in Rails. Typically, the views share their name with the associated controller action, as you can see above.
-For example, the index controller action of the `posts_controller.rb` will use the `index.html.erb` view file in the `app/views/posts` directory.
+For example, the index controller action of the `articles_controller.rb` will use the `index.html.erb` view file in the `app/views/articles` directory.
The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of these three components.
@@ -276,23 +276,23 @@ Partial Layouts
Partials can have their own layouts applied to them. These layouts are different than the ones that are specified globally for the entire action, but they work in a similar fashion.
-Let's say we're displaying a post on a page, that should be wrapped in a `div` for display purposes. First, we'll create a new `Post`:
+Let's say we're displaying an article on a page, that should be wrapped in a `div` for display purposes. First, we'll create a new `Article`:
```ruby
-Post.create(body: 'Partial Layouts are cool!')
+Article.create(body: 'Partial Layouts are cool!')
```
-In the `show` template, we'll render the `_post` partial wrapped in the `box` layout:
+In the `show` template, we'll render the `_article` partial wrapped in the `box` layout:
-**posts/show.html.erb**
+**articles/show.html.erb**
```erb
-<%= render partial: 'post', layout: 'box', locals: {post: @post} %>
+<%= render partial: 'article', layout: 'box', locals: {article: @article} %>
```
-The `box` layout simply wraps the `_post` partial in a `div`:
+The `box` layout simply wraps the `_article` partial in a `div`:
-**posts/_box.html.erb**
+**articles/_box.html.erb**
```html+erb
<div class='box'>
@@ -300,13 +300,13 @@ The `box` layout simply wraps the `_post` partial in a `div`:
</div>
```
-The `_post` partial wraps the post's `body` in a `div` with the `id` of the post using the `div_for` helper:
+The `_article` partial wraps the article's `body` in a `div` with the `id` of the article using the `div_for` helper:
-**posts/_post.html.erb**
+**articles/_article.html.erb**
```html+erb
-<%= div_for(post) do %>
- <p><%= post.body %></p>
+<%= div_for(article) do %>
+ <p><%= article.body %></p>
<% end %>
```
@@ -314,22 +314,22 @@ this would output the following:
```html
<div class='box'>
- <div id='post_1'>
+ <div id='article_1'>
<p>Partial Layouts are cool!</p>
</div>
</div>
```
-Note that the partial layout has access to the local `post` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix.
+Note that the partial layout has access to the local `article` variable that was passed into the `render` call. However, unlike application-wide layouts, partial layouts still have the underscore prefix.
-You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_post` partial, we could do this instead:
+You can also render a block of code within a partial layout instead of calling `yield`. For example, if we didn't have the `_article` partial, we could do this instead:
-**posts/show.html.erb**
+**articles/show.html.erb**
```html+erb
-<% render(layout: 'box', locals: {post: @post}) do %>
- <%= div_for(post) do %>
- <p><%= post.body %></p>
+<% render(layout: 'box', locals: {article: @article}) do %>
+ <%= div_for(article) do %>
+ <p><%= article.body %></p>
<% end %>
<% end %>
```
@@ -356,18 +356,18 @@ This module provides methods for generating container tags, such as `div`, for y
Renders a container tag that relates to your Active Record Object.
-For example, given `@post` is the object of `Post` class, you can do:
+For example, given `@article` is the object of `Article` class, you can do:
```html+erb
-<%= content_tag_for(:tr, @post) do %>
- <td><%= @post.title %></td>
+<%= content_tag_for(:tr, @article) do %>
+ <td><%= @article.title %></td>
<% end %>
```
This will generate this HTML output:
```html
-<tr id="post_1234" class="post">
+<tr id="article_1234" class="article">
<td>Hello World!</td>
</tr>
```
@@ -375,34 +375,34 @@ This will generate this HTML output:
You can also supply HTML attributes as an additional option hash. For example:
```html+erb
-<%= content_tag_for(:tr, @post, class: "frontpage") do %>
- <td><%= @post.title %></td>
+<%= content_tag_for(:tr, @article, class: "frontpage") do %>
+ <td><%= @article.title %></td>
<% end %>
```
Will generate this HTML output:
```html
-<tr id="post_1234" class="post frontpage">
+<tr id="article_1234" class="article frontpage">
<td>Hello World!</td>
</tr>
```
-You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@posts` is an array of two `Post` objects:
+You can pass a collection of Active Record objects. This method will loop through your objects and create a container for each of them. For example, given `@articles` is an array of two `Article` objects:
```html+erb
-<%= content_tag_for(:tr, @posts) do |post| %>
- <td><%= post.title %></td>
+<%= content_tag_for(:tr, @articles) do |article| %>
+ <td><%= article.title %></td>
<% end %>
```
Will generate this HTML output:
```html
-<tr id="post_1234" class="post">
+<tr id="article_1234" class="article">
<td>Hello World!</td>
</tr>
-<tr id="post_1235" class="post">
+<tr id="article_1235" class="article">
<td>Ruby on Rails Rocks!</td>
</tr>
```
@@ -412,15 +412,15 @@ Will generate this HTML output:
This is actually a convenient method which calls `content_tag_for` internally with `:div` as the tag name. You can pass either an Active Record object or a collection of objects. For example:
```html+erb
-<%= div_for(@post, class: "frontpage") do %>
- <td><%= @post.title %></td>
+<%= div_for(@article, class: "frontpage") do %>
+ <td><%= @article.title %></td>
<% end %>
```
Will generate this HTML output:
```html
-<div id="post_1234" class="post frontpage">
+<div id="article_1234" class="article frontpage">
<td>Hello World!</td>
</div>
```
@@ -590,14 +590,14 @@ This helper makes building an Atom feed easy. Here's a full usage example:
**config/routes.rb**
```ruby
-resources :posts
+resources :articles
```
-**app/controllers/posts_controller.rb**
+**app/controllers/articles_controller.rb**
```ruby
def index
- @posts = Post.all
+ @articles = Article.all
respond_to do |format|
format.html
@@ -606,20 +606,20 @@ def index
end
```
-**app/views/posts/index.atom.builder**
+**app/views/articles/index.atom.builder**
```ruby
atom_feed do |feed|
- feed.title("Posts Index")
- feed.updated((@posts.first.created_at))
+ feed.title("Articles Index")
+ feed.updated((@articles.first.created_at))
- @posts.each do |post|
- feed.entry(post) do |entry|
- entry.title(post.title)
- entry.content(post.body, type: 'html')
+ @articles.each do |article|
+ feed.entry(article) do |entry|
+ entry.title(article.title)
+ entry.content(article.body, type: 'html')
entry.author do |author|
- author.name(post.author_name)
+ author.name(article.author_name)
end
end
end
@@ -697,7 +697,7 @@ For example, let's say we have a standard application layout, but also a special
</html>
```
-**app/views/posts/special.html.erb**
+**app/views/articles/special.html.erb**
```html+erb
<p>This is a special page.</p>
@@ -714,7 +714,7 @@ For example, let's say we have a standard application layout, but also a special
Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute.
```ruby
-date_select("post", "published_on")
+date_select("article", "published_on")
```
#### datetime_select
@@ -722,7 +722,7 @@ date_select("post", "published_on")
Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based attribute.
```ruby
-datetime_select("post", "published_on")
+datetime_select("article", "published_on")
```
#### distance_of_time_in_words
@@ -904,10 +904,10 @@ The params hash has a nested person value, which can therefore be accessed with
Returns a checkbox tag tailored for accessing a specified attribute.
```ruby
-# Let's say that @post.validated? is 1:
-check_box("post", "validated")
-# => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
-# <input name="post[validated]" type="hidden" value="0" />
+# Let's say that @article.validated? is 1:
+check_box("article", "validated")
+# => <input type="checkbox" id="article_validated" name="article[validated]" value="1" />
+# <input name="article[validated]" type="hidden" value="0" />
```
#### fields_for
@@ -939,7 +939,7 @@ file_field(:user, :avatar)
Creates a form and a scope around a specific model object that is used as a base for questioning about values for the fields.
```html+erb
-<%= form_for @post do |f| %>
+<%= form_for @article do |f| %>
<%= f.label :title, 'Title' %>:
<%= f.text_field :title %><br>
<%= f.label :body, 'Body' %>:
@@ -961,8 +961,8 @@ hidden_field(:user, :token)
Returns a label tag tailored for labelling an input field for a specified attribute.
```ruby
-label(:post, :title)
-# => <label for="post_title">Title</label>
+label(:article, :title)
+# => <label for="article_title">Title</label>
```
#### password_field
@@ -979,11 +979,11 @@ password_field(:login, :pass)
Returns a radio button tag for accessing a specified attribute.
```ruby
-# Let's say that @post.category returns "rails":
-radio_button("post", "category", "rails")
-radio_button("post", "category", "java")
-# => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
-# <input type="radio" id="post_category_java" name="post[category]" value="java" />
+# Let's say that @article.category returns "rails":
+radio_button("article", "category", "rails")
+radio_button("article", "category", "java")
+# => <input type="radio" id="article_category_rails" name="article[category]" value="rails" checked="checked" />
+# <input type="radio" id="article_category_java" name="article[category]" value="java" />
```
#### text_area
@@ -1002,8 +1002,8 @@ text_area(:comment, :text, size: "20x30")
Returns an input tag of the "text" type tailored for accessing a specified attribute.
```ruby
-text_field(:post, :title)
-# => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" />
+text_field(:article, :title)
+# => <input type="text" id="article_title" name="article[title]" value="#{@article.title}" />
```
#### email_field
@@ -1035,28 +1035,28 @@ Returns `select` and `option` tags for the collection of existing return values
Example object structure for use with this method:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
- has_many :posts
+ has_many :articles
def name_with_initial
"#{first_name.first}. #{last_name}"
end
end
```
-Sample usage (selecting the associated Author for an instance of Post, `@post`):
+Sample usage (selecting the associated Author for an instance of Article, `@article`):
```ruby
-collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {prompt: true})
+collection_select(:article, :author_id, Author.all, :id, :name_with_initial, {prompt: true})
```
-If `@post.author_id` is 1, this would return:
+If `@article.author_id` is 1, this would return:
```html
-<select name="post[author_id]">
+<select name="article[author_id]">
<option value="">Please select</option>
<option value="1" selected="selected">D. Heinemeier Hansson</option>
<option value="2">D. Thomas</option>
@@ -1071,33 +1071,33 @@ Returns `radio_button` tags for the collection of existing return values of `met
Example object structure for use with this method:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
- has_many :posts
+ has_many :articles
def name_with_initial
"#{first_name.first}. #{last_name}"
end
end
```
-Sample usage (selecting the associated Author for an instance of Post, `@post`):
+Sample usage (selecting the associated Author for an instance of Article, `@article`):
```ruby
-collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
+collection_radio_buttons(:article, :author_id, Author.all, :id, :name_with_initial)
```
-If `@post.author_id` is 1, this would return:
+If `@article.author_id` is 1, this would return:
```html
-<input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
-<label for="post_author_id_1">D. Heinemeier Hansson</label>
-<input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
-<label for="post_author_id_2">D. Thomas</label>
-<input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
-<label for="post_author_id_3">M. Clark</label>
+<input id="article_author_id_1" name="article[author_id]" type="radio" value="1" checked="checked" />
+<label for="article_author_id_1">D. Heinemeier Hansson</label>
+<input id="article_author_id_2" name="article[author_id]" type="radio" value="2" />
+<label for="article_author_id_2">D. Thomas</label>
+<input id="article_author_id_3" name="article[author_id]" type="radio" value="3" />
+<label for="article_author_id_3">M. Clark</label>
```
#### collection_check_boxes
@@ -1107,34 +1107,34 @@ Returns `check_box` tags for the collection of existing return values of `method
Example object structure for use with this method:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
has_and_belongs_to_many :authors
end
class Author < ActiveRecord::Base
- has_and_belongs_to_many :posts
+ has_and_belongs_to_many :articles
def name_with_initial
"#{first_name.first}. #{last_name}"
end
end
```
-Sample usage (selecting the associated Authors for an instance of Post, `@post`):
+Sample usage (selecting the associated Authors for an instance of Article, `@article`):
```ruby
-collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
+collection_check_boxes(:article, :author_ids, Author.all, :id, :name_with_initial)
```
-If `@post.author_ids` is [1], this would return:
+If `@article.author_ids` is [1], this would return:
```html
-<input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
-<label for="post_author_ids_1">D. Heinemeier Hansson</label>
-<input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
-<label for="post_author_ids_2">D. Thomas</label>
-<input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
-<label for="post_author_ids_3">M. Clark</label>
-<input name="post[author_ids][]" type="hidden" value="" />
+<input id="article_author_ids_1" name="article[author_ids][]" type="checkbox" value="1" checked="checked" />
+<label for="article_author_ids_1">D. Heinemeier Hansson</label>
+<input id="article_author_ids_2" name="article[author_ids][]" type="checkbox" value="2" />
+<label for="article_author_ids_2">D. Thomas</label>
+<input id="article_author_ids_3" name="article[author_ids][]" type="checkbox" value="3" />
+<label for="article_author_ids_3">M. Clark</label>
+<input name="article[author_ids][]" type="hidden" value="" />
```
#### country_options_for_select
@@ -1222,13 +1222,13 @@ Create a select tag and a series of contained option tags for the provided objec
Example:
```ruby
-select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true})
+select("article", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: true})
```
-If `@post.person_id` is 1, this would become:
+If `@article.person_id` is 1, this would become:
```html
-<select name="post[person_id]">
+<select name="article[person_id]">
<option value=""></option>
<option value="1" selected="selected">David</option>
<option value="2">Sam</option>
@@ -1303,10 +1303,10 @@ file_field_tag 'attachment'
Starts a form tag that points the action to an url configured with `url_for_options` just like `ActionController::Base#url_for`.
```html+erb
-<%= form_tag '/posts' do %>
+<%= form_tag '/articles' do %>
<div><%= submit_tag 'Save' %></div>
<% end %>
-# => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
+# => <form action="/articles" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
```
#### hidden_field_tag
@@ -1368,8 +1368,8 @@ select_tag "people", "<option>David</option>"
Creates a submit button with the text provided as the caption.
```ruby
-submit_tag "Publish this post"
-# => <input name="commit" type="submit" value="Publish this post" />
+submit_tag "Publish this article"
+# => <input name="commit" type="submit" value="Publish this article" />
```
#### text_area_tag
@@ -1377,8 +1377,8 @@ submit_tag "Publish this post"
Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
```ruby
-text_area_tag 'post'
-# => <textarea id="post" name="post"></textarea>
+text_area_tag 'article'
+# => <textarea id="article" name="article"></textarea>
```
#### text_field_tag
@@ -1602,7 +1602,7 @@ Localized Views
Action View has the ability render different templates depending on the current locale.
-For example, suppose you have a `PostsController` with a show action. By default, calling this action will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/posts/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available.
+For example, suppose you have a `ArticlesController` with a show action. By default, calling this action will render `app/views/articles/show.html.erb`. But if you set `I18n.locale = :de`, then `app/views/articles/show.de.html.erb` will be rendered instead. If the localized template isn't present, the undecorated version will be used. This means you're not required to provide localized views for all cases, but they will be preferred and used if available.
You can use the same technique to localize the rescue files in your public directory. For example, setting `I18n.locale = :de` and creating `public/500.de.html` and `public/404.de.html` would allow you to have localized rescue pages.
@@ -1616,6 +1616,6 @@ def set_expert_locale
end
```
-Then you could create special views like `app/views/posts/show.expert.html.erb` that would only be displayed to expert users.
+Then you could create special views like `app/views/articles/show.expert.html.erb` that would only be displayed to expert users.
You can read more about the Rails Internationalization (I18n) API [here](i18n.html).
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index a184f0753d..21022f1abb 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -82,13 +82,13 @@ by underscores. Examples:
* Model Class - Singular with the first letter of each word capitalized (e.g.,
`BookClub`).
-| Model / Class | Table / Schema |
-| ------------- | -------------- |
-| `Post` | `posts` |
-| `LineItem` | `line_items` |
-| `Deer` | `deers` |
-| `Mouse` | `mice` |
-| `Person` | `people` |
+| Model / Class | Table / Schema |
+| ---------------- | -------------- |
+| `Article` | `articles` |
+| `LineItem` | `line_items` |
+| `Deer` | `deers` |
+| `Mouse` | `mice` |
+| `Person` | `people` |
### Schema Conventions
@@ -120,9 +120,9 @@ to Active Record instances:
* `(association_name)_type` - Stores the type for
[polymorphic associations](association_basics.html#polymorphic-associations).
* `(table_name)_count` - Used to cache the number of belonging objects on
- associations. For example, a `comments_count` column in a `Post` class that
+ associations. For example, a `comments_count` column in a `Articles` class that
has many instances of `Comment` will cache the number of existent comments
- for each post.
+ for each article.
NOTE: While these column names are optional, they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, `type` is a reserved keyword used to designate a table using Single Table Inheritance (STI). If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling.
@@ -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 fbcce325ed..f0ae3c729e 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -261,27 +261,27 @@ WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by
Relational Callbacks
--------------------
-Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many posts. A user's posts should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Post` model:
+Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many articles. A user's articles should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Article` model:
```ruby
class User < ActiveRecord::Base
- has_many :posts, dependent: :destroy
+ has_many :articles, dependent: :destroy
end
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
after_destroy :log_destroy_action
def log_destroy_action
- puts 'Post destroyed'
+ puts 'Article destroyed'
end
end
>> user = User.first
=> #<User id: 1>
->> user.posts.create!
-=> #<Post id: 1, user_id: 1>
+>> user.articles.create!
+=> #<Article id: 1, user_id: 1>
>> user.destroy
-Post destroyed
+Article destroyed
=> #<User id: 1>
```
@@ -328,7 +328,7 @@ When writing conditional callbacks, it is possible to mix both `:if` and `:unles
```ruby
class Comment < ActiveRecord::Base
after_create :send_email_to_author, if: :author_wants_emails?,
- unless: Proc.new { |comment| comment.post.ignore_comments? }
+ unless: Proc.new { |comment| comment.article.ignore_comments? }
end
```
diff --git a/guides/source/migrations.md b/guides/source/active_record_migrations.md
index c61ccfe94a..5a550d9e55 100644
--- a/guides/source/migrations.md
+++ b/guides/source/active_record_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
@@ -293,21 +293,15 @@ end
You can append as many column name/type pairs as you want.
-### Supported Type Modifiers
+### Passing Modifiers
-You can also specify some options just after the field type between curly
-braces. You can use the following modifiers:
-
-* `limit` Sets the maximum size of the `string/text/binary/integer` fields.
-* `precision` Defines the precision for the `decimal` fields, representing the total number of digits in the number.
-* `scale` Defines the scale for the `decimal` fields, representing the number of digits after the decimal point.
-* `polymorphic` Adds a `type` column for `belongs_to` associations.
-* `null` Allows or disallows `NULL` values in the column.
+Some commonly used [type modifiers](#column-modifiers) can be passed directly on
+the command line. They are enclosed by curly braces and follow the field type:
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
@@ -321,6 +315,8 @@ class AddDetailsToProducts < ActiveRecord::Migration
end
```
+TIP: Have a look at the generators help output for further details.
+
Writing a Migration
-------------------
@@ -415,6 +411,44 @@ end
removes the `description` and `name` columns, creates a `part_number` string
column and adds an index on it. Finally it renames the `upccode` column.
+### Changing Columns
+
+Like the `remove_column` and `add_column` Rails provides the `change_column`
+migration method.
+
+```ruby
+change_column :products, :part_number, :text
+```
+
+This changes the column `part_number` on products table to be a `:text` field.
+
+Besides `change_column`, the `change_column_null` and `change_column_default`
+methods are used specifically to change the null and default values of a
+column.
+
+```ruby
+change_column_null :products, :name, false
+change_column_default :products, :approved, false
+```
+
+This sets `:name` field on products to a `NOT NULL` column and the default
+value of the `:approved` field to false.
+
+### Column Modifiers
+
+Column modifiers can be applied when creating or changing a column:
+
+* `limit` Sets the maximum size of the `string/text/binary/integer` fields.
+* `precision` Defines the precision for the `decimal` fields, representing the total number of digits in the number.
+* `scale` Defines the scale for the `decimal` fields, representing the number of digits after the decimal point.
+* `polymorphic` Adds a `type` column for `belongs_to` associations.
+* `null` Allows or disallows `NULL` values in the column.
+* `default` Allows to set a default value on the column. NOTE: If using a dynamic value (such as date), the default will only be calculated the first time (e.g. on the date the migration is applied.)
+* `index` Adds an index for the column.
+
+Some adapters may support additional options; see the adapter specific API docs
+for further information.
+
### When Helpers aren't Enough
If the helpers provided by Active Record aren't enough you can use the `execute`
@@ -653,7 +687,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
@@ -670,7 +704,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`
@@ -678,7 +712,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.
@@ -688,7 +722,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
@@ -718,7 +752,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
@@ -734,7 +768,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
@@ -902,6 +936,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/active_record_postgresql.md b/guides/source/active_record_postgresql.md
new file mode 100644
index 0000000000..a5649e3903
--- /dev/null
+++ b/guides/source/active_record_postgresql.md
@@ -0,0 +1,437 @@
+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.
+* How to back your Active Record models with database views.
+
+--------------------------------------------------------------------------------
+
+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 :books do |t|
+ t.string 'title'
+ t.string 'tags', array: true
+ t.integer 'ratings', array: true
+end
+add_index :books, :tags, using: 'gin'
+add_index :books, :ratings, using: 'gin'
+
+# 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, with the exception of `points` are mapped to normal text.
+A point is casted to an array containing `x` and `y` coordinates.
+
+
+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")
+```
+
+Database 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!
+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_querying.md b/guides/source/active_record_querying.md
index 2a76df156c..486e7b80ff 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -472,8 +472,8 @@ Client.where('locked' => true)
In the case of a belongs_to relationship, an association key can be used to specify the model if an Active Record object is used as the value. This method works with polymorphic relationships as well.
```ruby
-Post.where(author: author)
-Author.joins(:posts).where(posts: { author: author })
+Article.where(author: author)
+Author.joins(:articles).where(articles: { author: author })
```
NOTE: The values cannot be symbols. For example, you cannot do `Client.where(status: :active)`.
@@ -511,7 +511,7 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
`NOT` SQL queries can be built by `where.not`.
```ruby
-Post.where.not(author: author)
+Article.where.not(author: author)
```
In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions.
@@ -659,6 +659,23 @@ FROM orders
GROUP BY date(created_at)
```
+### Total of grouped items
+
+To get the total of grouped items on a single query call `count` after the `group`.
+
+```ruby
+Order.group(:status).count
+# => { 'awaiting_approval' => 7, 'paid' => 12 }
+```
+
+The SQL that would be executed would be something like this:
+
+```sql
+SELECT COUNT (*) AS count_all, status AS status
+FROM "orders"
+GROUP BY status
+```
+
Having
------
@@ -690,32 +707,32 @@ Overriding Conditions
You can specify certain conditions to be removed using the `unscope` method. For example:
```ruby
-Post.where('id > 10').limit(20).order('id asc').except(:order)
+Article.where('id > 10').limit(20).order('id asc').except(:order)
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE id > 10 LIMIT 20
+SELECT * FROM articles WHERE id > 10 LIMIT 20
# Original query without `unscope`
-SELECT * FROM posts WHERE id > 10 ORDER BY id asc LIMIT 20
+SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20
```
You can additionally unscope specific where clauses. For example:
```ruby
-Post.where(id: 10, trashed: false).unscope(where: :id)
-# SELECT "posts".* FROM "posts" WHERE trashed = 0
+Article.where(id: 10, trashed: false).unscope(where: :id)
+# SELECT "articles".* FROM "articles" WHERE trashed = 0
```
A relation which has used `unscope` will affect any relation it is
merged in to:
```ruby
-Post.order('id asc').merge(Post.unscope(:order))
-# SELECT "posts".* FROM "posts"
+Article.order('id asc').merge(Article.unscope(:order))
+# SELECT "articles".* FROM "articles"
```
### `only`
@@ -723,16 +740,16 @@ Post.order('id asc').merge(Post.unscope(:order))
You can also override conditions using the `only` method. For example:
```ruby
-Post.where('id > 10').limit(20).order('id desc').only(:order, :where)
+Article.where('id > 10').limit(20).order('id desc').only(:order, :where)
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE id > 10 ORDER BY id DESC
+SELECT * FROM articles WHERE id > 10 ORDER BY id DESC
# Original query without `only`
-SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20
+SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20
```
@@ -741,25 +758,27 @@ SELECT "posts".* FROM "posts" WHERE (id > 10) ORDER BY id desc LIMIT 20
The `reorder` method overrides the default scope order. For example:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
..
..
has_many :comments, -> { order('posted_at DESC') }
end
-Post.find(10).comments.reorder('name')
+Article.find(10).comments.reorder('name')
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE id = 10 ORDER BY name
+SELECT * FROM articles WHERE id = 10
+SELECT * FROM comments WHERE article_id = 10 ORDER BY name
```
In case the `reorder` clause is not used, the SQL executed would be:
```sql
-SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC
+SELECT * FROM articles WHERE id = 10
+SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC
```
### `reverse_order`
@@ -795,25 +814,25 @@ This method accepts **no** arguments.
The `rewhere` method overrides an existing, named where condition. For example:
```ruby
-Post.where(trashed: true).rewhere(trashed: false)
+Article.where(trashed: true).rewhere(trashed: false)
```
The SQL that would be executed:
```sql
-SELECT * FROM posts WHERE `trashed` = 0
+SELECT * FROM articles WHERE `trashed` = 0
```
In case the `rewhere` clause is not used,
```ruby
-Post.where(trashed: true).where(trashed: false)
+Article.where(trashed: true).where(trashed: false)
```
the SQL executed would be:
```sql
-SELECT * FROM posts WHERE `trashed` = 1 AND `trashed` = 0
+SELECT * FROM articles WHERE `trashed` = 1 AND `trashed` = 0
```
Null Relation
@@ -822,21 +841,21 @@ Null Relation
The `none` method returns a chainable relation with no records. Any subsequent conditions chained to the returned relation will continue generating empty relations. This is useful in scenarios where you need a chainable response to a method or a scope that could return zero results.
```ruby
-Post.none # returns an empty Relation and fires no queries.
+Article.none # returns an empty Relation and fires no queries.
```
```ruby
-# The visible_posts method below is expected to return a Relation.
-@posts = current_user.visible_posts.where(name: params[:name])
+# The visible_articles method below is expected to return a Relation.
+@articles = current_user.visible_articles.where(name: params[:name])
-def visible_posts
+def visible_articles
case role
when 'Country Manager'
- Post.where(country: country)
+ Article.where(country: country)
when 'Reviewer'
- Post.published
+ Article.published
when 'Bad User'
- Post.none # => returning [] or nil breaks the caller code in this case
+ Article.none # => returning [] or nil breaks the caller code in this case
end
end
```
@@ -963,21 +982,21 @@ WARNING: This method only works with `INNER JOIN`.
Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method.
-For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models:
+For example, consider the following `Category`, `Article`, `Comment`, `Guest` and `Tag` models:
```ruby
class Category < ActiveRecord::Base
- has_many :posts
+ has_many :articles
end
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
belongs_to :category
has_many :comments
has_many :tags
end
class Comment < ActiveRecord::Base
- belongs_to :post
+ belongs_to :article
has_one :guest
end
@@ -986,7 +1005,7 @@ class Guest < ActiveRecord::Base
end
class Tag < ActiveRecord::Base
- belongs_to :post
+ belongs_to :article
end
```
@@ -995,64 +1014,64 @@ Now all of the following will produce the expected join queries using `INNER JOI
#### Joining a Single Association
```ruby
-Category.joins(:posts)
+Category.joins(:articles)
```
This produces:
```sql
SELECT categories.* FROM categories
- INNER JOIN posts ON posts.category_id = categories.id
+ INNER JOIN articles ON articles.category_id = categories.id
```
-Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use `Category.joins(:posts).uniq`.
+Or, in English: "return a Category object for all categories with articles". Note that you will see duplicate categories if more than one article has the same category. If you want unique categories, you can use `Category.joins(:articles).uniq`.
#### Joining Multiple Associations
```ruby
-Post.joins(:category, :comments)
+Article.joins(:category, :comments)
```
This produces:
```sql
-SELECT posts.* FROM posts
- INNER JOIN categories ON posts.category_id = categories.id
- INNER JOIN comments ON comments.post_id = posts.id
+SELECT articles.* FROM articles
+ INNER JOIN categories ON articles.category_id = categories.id
+ INNER JOIN comments ON comments.article_id = articles.id
```
-Or, in English: "return all posts that have a category and at least one comment". Note again that posts with multiple comments will show up multiple times.
+Or, in English: "return all articles that have a category and at least one comment". Note again that articles with multiple comments will show up multiple times.
#### Joining Nested Associations (Single Level)
```ruby
-Post.joins(comments: :guest)
+Article.joins(comments: :guest)
```
This produces:
```sql
-SELECT posts.* FROM posts
- INNER JOIN comments ON comments.post_id = posts.id
+SELECT articles.* FROM articles
+ INNER JOIN comments ON comments.article_id = articles.id
INNER JOIN guests ON guests.comment_id = comments.id
```
-Or, in English: "return all posts that have a comment made by a guest."
+Or, in English: "return all articles that have a comment made by a guest."
#### Joining Nested Associations (Multiple Level)
```ruby
-Category.joins(posts: [{ comments: :guest }, :tags])
+Category.joins(articles: [{ comments: :guest }, :tags])
```
This produces:
```sql
SELECT categories.* FROM categories
- INNER JOIN posts ON posts.category_id = categories.id
- INNER JOIN comments ON comments.post_id = posts.id
+ INNER JOIN articles ON articles.category_id = categories.id
+ INNER JOIN comments ON comments.article_id = articles.id
INNER JOIN guests ON guests.comment_id = comments.id
- INNER JOIN tags ON tags.post_id = posts.id
+ INNER JOIN tags ON tags.article_id = articles.id
```
### Specifying Conditions on the Joined Tables
@@ -1121,18 +1140,18 @@ Active Record lets you eager load any number of associations with a single `Mode
#### Array of Multiple Associations
```ruby
-Post.includes(:category, :comments)
+Article.includes(:category, :comments)
```
-This loads all the posts and the associated category and comments for each post.
+This loads all the articles and the associated category and comments for each article.
#### Nested Associations Hash
```ruby
-Category.includes(posts: [{ comments: :guest }, :tags]).find(1)
+Category.includes(articles: [{ comments: :guest }, :tags]).find(1)
```
-This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association.
+This will find the category with id 1 and eager load all of the associated articles, the associated articles' tags and comments, and every comment's guest association.
### Specifying Conditions on Eager Loaded Associations
@@ -1141,18 +1160,31 @@ Even though Active Record lets you specify conditions on the eager loaded associ
However if you must do this, you may use `where` as you would normally.
```ruby
-Post.includes(:comments).where("comments.visible" => true)
+Article.includes(:comments).where(comments: { visible: true })
```
-This would generate a query which contains a `LEFT OUTER JOIN` whereas the `joins` method would generate one using the `INNER JOIN` function instead.
+This would generate a query which contains a `LEFT OUTER JOIN` whereas the
+`joins` method would generate one using the `INNER JOIN` function instead.
```ruby
- SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1)
+ SELECT "articles"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "articles" LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id" WHERE (comments.visible = 1)
```
If there was no `where` condition, this would generate the normal set of two queries.
-If, in the case of this `includes` query, there were no comments for any posts, all the posts would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned.
+NOTE: Using `where` like this will only work when you pass it a Hash. For
+SQL-fragments you need use `references` to force joined tables:
+
+```ruby
+Article.includes(:comments).where("comments.visible = true").references(:comments)
+```
+
+If, in the case of this `includes` query, there were no comments for any
+articles, all the articles would still be loaded. By using `joins` (an INNER
+JOIN), the join conditions **must** match, otherwise no records will be
+returned.
+
+
Scopes
------
@@ -1162,7 +1194,7 @@ Scoping allows you to specify commonly-used queries which can be referenced as m
To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
end
```
@@ -1170,7 +1202,7 @@ end
This is exactly the same as defining a class method, and which you use is a matter of personal preference:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
def self.published
where(published: true)
end
@@ -1180,7 +1212,7 @@ end
Scopes are also chainable within scopes:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :published_and_commented, -> { published.where("comments_count > 0") }
end
@@ -1189,14 +1221,14 @@ end
To call this `published` scope we can call it on either the class:
```ruby
-Post.published # => [published posts]
+Article.published # => [published articles]
```
-Or on an association consisting of `Post` objects:
+Or on an association consisting of `Article` objects:
```ruby
category = Category.first
-category.posts.published # => [published posts belonging to this category]
+category.articles.published # => [published articles belonging to this category]
```
### Passing in arguments
@@ -1204,7 +1236,7 @@ category.posts.published # => [published posts belonging to this category]
Your scope can take arguments:
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
```
@@ -1212,13 +1244,13 @@ end
Call the scope as if it were a class method:
```ruby
-Post.created_before(Time.zone.now)
+Article.created_before(Time.zone.now)
```
However, this is just duplicating the functionality that would be provided to you by a class method.
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
def self.created_before(time)
where("created_at < ?", time)
end
@@ -1228,7 +1260,7 @@ end
Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects:
```ruby
-category.posts.created_before(time)
+category.articles.created_before(time)
```
### Applying a default scope
@@ -1591,20 +1623,20 @@ You can also use `any?` and `many?` to check for existence on a model or relatio
```ruby
# via a model
-Post.any?
-Post.many?
+Article.any?
+Article.many?
# via a named scope
-Post.recent.any?
-Post.recent.many?
+Article.recent.any?
+Article.recent.many?
# via a relation
-Post.where(published: true).any?
-Post.where(published: true).many?
+Article.where(published: true).any?
+Article.where(published: true).many?
# via an association
-Post.first.categories.any?
-Post.first.categories.many?
+Article.first.categories.any?
+Article.first.categories.many?
```
Calculations
@@ -1694,19 +1726,26 @@ Running EXPLAIN
You can run EXPLAIN on the queries triggered by relations. For example,
```ruby
-User.where(id: 1).joins(:posts).explain
+User.where(id: 1).joins(:articles).explain
```
may yield
```
-EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
-| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
-| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `articles` ON `articles`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
++----+-------------+----------+-------+---------------+
+| id | select_type | table | type | possible_keys |
++----+-------------+----------+-------+---------------+
+| 1 | SIMPLE | users | const | PRIMARY |
+| 1 | SIMPLE | articles | ALL | NULL |
++----+-------------+----------+-------+---------------+
++---------+---------+-------+------+-------------+
+| key | key_len | ref | rows | Extra |
++---------+---------+-------+------+-------------+
+| PRIMARY | 4 | const | 1 | |
+| NULL | NULL | NULL | 1 | Using where |
++---------+---------+-------+------+-------------+
+
2 rows in set (0.00 sec)
```
@@ -1716,15 +1755,15 @@ Active Record performs a pretty printing that emulates the one of the database
shells. So, the same query running with the PostgreSQL adapter would yield instead
```
-EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
+EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "articles" ON "articles"."user_id" = "users"."id" WHERE "users"."id" = 1
QUERY PLAN
------------------------------------------------------------------------------
Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
- Join Filter: (posts.user_id = users.id)
+ Join Filter: (articles.user_id = users.id)
-> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
Index Cond: (id = 1)
- -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
- Filter: (posts.user_id = 1)
+ -> Seq Scan on articles (cost=0.00..28.88 rows=8 width=4)
+ Filter: (articles.user_id = 1)
(6 rows)
```
@@ -1733,26 +1772,39 @@ may need the results of previous ones. Because of that, `explain` actually
executes the query, and then asks for the query plans. For example,
```ruby
-User.where(id: 1).includes(:posts).explain
+User.where(id: 1).includes(:articles).explain
```
yields
```
EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
-| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
-| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
-+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
++----+-------------+-------+-------+---------------+
+| id | select_type | table | type | possible_keys |
++----+-------------+-------+-------+---------------+
+| 1 | SIMPLE | users | const | PRIMARY |
++----+-------------+-------+-------+---------------+
++---------+---------+-------+------+-------+
+| key | key_len | ref | rows | Extra |
++---------+---------+-------+------+-------+
+| PRIMARY | 4 | const | 1 | |
++---------+---------+-------+------+-------+
+
1 row in set (0.00 sec)
-EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
-+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
-| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
-+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
-| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
-+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
+EXPLAIN for: SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` IN (1)
++----+-------------+----------+------+---------------+
+| id | select_type | table | type | possible_keys |
++----+-------------+----------+------+---------------+
+| 1 | SIMPLE | articles | ALL | NULL |
++----+-------------+----------+------+---------------+
++------+---------+------+------+-------------+
+| key | key_len | ref | rows | Extra |
++------+---------+------+------+-------------+
+| NULL | NULL | NULL | 1 | Using where |
++------+---------+------+------+-------------+
+
+
1 row in set (0.00 sec)
```
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index a483a6dd24..cb459626d5 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?
@@ -1129,15 +1129,15 @@ generating a scaffold, Rails will put some ERB into the `_form.html.erb` that
it generates that displays the full list of errors on that model.
Assuming we have a model that's been saved in an instance variable named
-`@post`, it looks like this:
+`@article`, it looks like this:
```ruby
-<% if @post.errors.any? %>
+<% if @article.errors.any? %>
<div id="error_explanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
+ <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul>
- <% @post.errors.full_messages.each do |msg| %>
+ <% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
@@ -1151,7 +1151,7 @@ the entry.
```
<div class="field_with_errors">
- <input id="post_title" name="post[title]" size="30" type="text" value="">
+ <input id="article_title" name="article[title]" size="30" type="text" value="">
</div>
```
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 87d780eca9..4f37bf971a 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`.
@@ -1106,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
@@ -1768,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
@@ -3823,7 +3838,7 @@ The name may be given as a symbol or string. A symbol is tested against the bare
TIP: A symbol can represent a fully-qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically.
-For example, when an action of `PostsController` is called Rails tries optimistically to use `PostsHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `posts_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases:
+For example, when an action of `ArticlesController` is called Rails tries optimistically to use `ArticlesHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `articles_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases:
```ruby
def default_helper_module!
@@ -3846,7 +3861,7 @@ Active Support adds `is_missing?` to `LoadError`, and also assigns that class to
Given a path name `is_missing?` tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension).
-For example, when an action of `PostsController` is called Rails tries to load `posts_helper.rb`, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method `is_missing?` provides a way to distinguish both cases:
+For example, when an action of `ArticlesController` is called Rails tries to load `articles_helper.rb`, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method `is_missing?` provides a way to distinguish both cases:
```ruby
def default_helper_module!
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 6c77a40d42..7033947468 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.
@@ -364,7 +364,7 @@ INFO. Options passed to fetch will be merged with the payload.
| ------ | --------------------- |
| `:key` | Key used in the store |
-INFO. Cache stores my add their own keys
+INFO. Cache stores may add their own keys
```ruby
{
@@ -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..7e9b288ffd 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -13,7 +13,19 @@ After reading this guide, you will know:
RDoc
----
-The Rails API documentation is generated with RDoc. Please consult the documentation for help with the [markup](http://rdoc.rubyforge.org/RDoc/Markup.html), and also take into account these [additional directives](http://rdoc.rubyforge.org/RDoc/Parser/Ruby.html).
+The [Rails API documentation](http://api.rubyonrails.org) is generated with
+[RDoc](http://docs.seattlerb.org/rdoc/).
+
+```bash
+ bundle exec rake rdoc
+```
+
+Resulting HTML files can be found in the ./doc/rdoc directory.
+
+Please consult the RDoc documentation for help with the
+[markup](http://docs.seattlerb.org/rdoc/RDoc/Markup.html),
+and also take into account these [additional
+directives](http://docs.seattlerb.org/rdoc/RDoc/Parser/Ruby.html).
Wording
-------
@@ -110,14 +122,14 @@ The results of expressions follow them and are introduced by "# => ", vertically
If a line is too long, the comment may be placed on the next line:
```ruby
-# label(:post, :title)
-# # => <label for="post_title">Title</label>
+# label(:article, :title)
+# # => <label for="article_title">Title</label>
#
-# label(:post, :title, "A short title")
-# # => <label for="post_title">A short title</label>
+# label(:article, :title, "A short title")
+# # => <label for="article_title">A short title</label>
#
-# label(:post, :title, "A short title", class: "title_label")
-# # => <label for="post_title" class="title_label">A short title</label>
+# label(:article, :title, "A short title", class: "title_label")
+# # => <label for="article_title" class="title_label">A short title</label>
```
Avoid using any printing methods like `puts` or `p` for that purpose.
@@ -175,8 +187,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 +298,64 @@ 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.
+
+Regarding the Rails Stack
+-------------------------
+
+When documenting parts of Rails API, it's important to remember all of the
+pieces that go into the Rails stack.
+
+This means that behavior may change depending on the scope or context of the
+method or class you're trying to document.
+
+In various places there is different behavior when you take the entire stack
+into account, one such example is
+`ActionView::Helpers::AssetTagHelper#image_tag`:
+
+```ruby
+# image_tag("icon.png")
+# # => <img alt="Icon" src="/assets/icon.png" />
+```
+
+Although the default behavior for `#image_tag` is to always return
+`/images/icon.png`, we take into account the full Rails stack (including the
+Asset Pipeline) we may see the result seen above.
+
+We're only concerned with the behavior experienced when using the full default
+Rails stack.
+
+In this case, we want to document the behavior of the _framework_, and not just
+this specific method.
+
+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 52fc9726d9..2d1548f252 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -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
@@ -581,8 +578,21 @@ runtime. To disable this behavior you can set:
config.assets.raise_runtime_errors = false
```
-When this option is true asset pipeline will check if all the assets loaded in your application
-are included in the `config.assets.precompile` list.
+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.digest` is also true, the asset pipeline will require that
+all requests for assets include digests.
+
+### Turning Digests Off
+
+You can turn off digests by updating `config/environments/development.rb` to
+include:
+
+```ruby
+config.assets.digest = false
+```
+
+When this option is true, digests will be generated for asset URLs.
### Turning Debugging Off
@@ -676,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.
@@ -699,7 +709,7 @@ The default matcher for compiling files includes `application.js`,
automatically) from `app/assets` folders including your gems:
```ruby
-[ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) },
+[ Proc.new { |filename, path| path =~ /app\/assets/ && !%w(.js .css).include?(File.extname(filename)) },
/application.(css|js)$/ ]
```
@@ -778,13 +788,15 @@ For Apache:
# `mod_expires` to be enabled.
<Location /assets/>
# Use of ETag is discouraged when Last-Modified is present
- Header unset ETag FileETag None
+ Header unset ETag
+ FileETag None
# RFC says only cache for 1 year
- ExpiresActive On ExpiresDefault "access plus 1 year"
+ ExpiresActive On
+ ExpiresDefault "access plus 1 year"
</Location>
```
-For nginx:
+For NGINX:
```nginx
location ~ ^/assets/ {
@@ -806,7 +818,7 @@ compression ratio, thus reducing the size of the data transfer to the minimum.
On the other hand, web servers can be configured to serve compressed content
directly from disk, rather than deflating non-compressed files themselves.
-Nginx is able to do this automatically enabling `gzip_static`:
+NGINX is able to do this automatically enabling `gzip_static`:
```nginx
location ~ ^/(assets)/ {
@@ -825,7 +837,7 @@ the module compiled. Otherwise, you may need to perform a manual compilation:
./configure --with-http_gzip_static_module
```
-If you're compiling nginx with Phusion Passenger you'll need to pass that option
+If you're compiling NGINX with Phusion Passenger you'll need to pass that option
when prompted.
A robust configuration for Apache is possible but tricky; please Google around.
@@ -844,10 +856,12 @@ duplication of work.
Local compilation allows you to commit the compiled files into source control,
and deploy as normal.
-There are two caveats:
+There are three caveats:
* You must not run the Capistrano deployment task that precompiles assets.
-* You must change the following two application configuration settings.
+* You must ensure any necessary compressors or minifiers are
+available on your development system.
+* You must change the following application configuration setting:
In `config/environments/development.rb`, place the following line:
@@ -861,9 +875,6 @@ development mode, and pass all requests to Sprockets. The prefix is still set to
would serve the precompiled assets from `/assets` in development, and you would
not see any local changes until you compile assets again.
-You will also need to ensure any necessary compressors or minifiers are
-available on your development system.
-
In practice, this will allow you to precompile locally, have those files in your
working tree, and commit those files to source control when needed. Development
mode will work as expected.
@@ -910,9 +921,17 @@ cache forever. This can cause problems. If you use
Every cache is different, so evaluate how your CDN handles caching and make sure
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
+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
------------------------
@@ -1006,12 +1025,12 @@ 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)
on how to use this feature.
-Apache and nginx support this option, which can be enabled in
+Apache and NGINX support this option, which can be enabled in
`config/environments/production.rb`:
```ruby
-# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
-# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
+# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
```
WARNING: If you are upgrading an existing application and intend to use this
@@ -1021,7 +1040,7 @@ and any other environments you define with production behavior (not
TIP: For further details have a look at the docs of your production web server:
- [Apache](https://tn123.org/mod_xsendfile/)
-- [Nginx](http://wiki.nginx.org/XSendfile)
+- [NGINX](http://wiki.nginx.org/XSendfile)
Assets Cache Store
------------------
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index df38bd7321..22f6f5e7d6 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1725,58 +1725,58 @@ mostly useful together with the `:through` option.
```ruby
class Person < ActiveRecord::Base
has_many :readings
- has_many :posts, through: :readings
+ has_many :articles, through: :readings
end
person = Person.create(name: 'John')
-post = Post.create(name: 'a1')
-person.posts << post
-person.posts << post
-person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
-Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]
+article = Article.create(name: 'a1')
+person.articles << article
+person.articles << article
+person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
```
-In the above case there are two readings and `person.posts` brings out both of
-them even though these records are pointing to the same post.
+In the above case there are two readings and `person.articles` brings out both of
+them even though these records are pointing to the same article.
Now let's set `distinct`:
```ruby
class Person
has_many :readings
- has_many :posts, -> { distinct }, through: :readings
+ has_many :articles, -> { distinct }, through: :readings
end
person = Person.create(name: 'Honda')
-post = Post.create(name: 'a1')
-person.posts << post
-person.posts << post
-person.posts.inspect # => [#<Post id: 7, name: "a1">]
-Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]
+article = Article.create(name: 'a1')
+person.articles << article
+person.articles << article
+person.articles.inspect # => [#<Article id: 7, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
```
-In the above case there are still two readings. However `person.posts` shows
-only one post because the collection loads only unique records.
+In the above case there are still two readings. However `person.articles` shows
+only one article because the collection loads only unique records.
If you want to make sure that, upon insertion, all of the records in the
persisted association are distinct (so that you can be sure that when you
inspect the association that you will never find duplicate records), you should
add a unique index on the table itself. For example, if you have a table named
-`person_posts` and you want to make sure all the posts are unique, you could
+`person_articles` and you want to make sure all the articles are unique, you could
add the following in a migration:
```ruby
-add_index :person_posts, :post, unique: true
+add_index :person_articles, :article, unique: true
```
Note that checking for uniqueness using something like `include?` is subject
to race conditions. Do not attempt to use `include?` to enforce distinctness
-in an association. For instance, using the post example from above, the
+in an association. For instance, using the article example from above, the
following code would be racy because multiple users could be attempting this
at the same time:
```ruby
-person.posts << post unless person.posts.include?(post)
+person.articles << article unless person.articles.include?(article)
```
#### When are Objects Saved?
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index b6423dd44e..3e39ecdad2 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -28,7 +28,7 @@ config.action_controller.perform_caching = true
### Page Caching
-Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
+Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or NGINX), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with.
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.
@@ -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 756c8f8b51..7e60f929a1 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]
...
@@ -123,25 +123,24 @@ Usage: rails generate controller NAME [action action] [options]
Description:
...
- To create a controller within a module, specify the controller name as a
- path like 'parent_module/controller_name'.
+ To create a controller within a module, specify the controller name as a path like 'parent_module/controller_name'.
...
Example:
- `rails generate controller CreditCard open debit credit close`
+ `rails generate controller CreditCards open debit credit close`
- Credit card controller with URLs like /credit_card/debit.
+ Credit card controller with URLs like /credit_cards/debit.
Controller: app/controllers/credit_card_controller.rb
- Test: test/controllers/credit_card_controller_test.rb
- Views: app/views/credit_card/debit.html.erb [...]
- Helper: app/helpers/credit_card_helper.rb
+ Test: test/controllers/credit_cards_controller_test.rb
+ Views: app/views/credit_cards/debit.html.erb [...]
+ Helper: app/helpers/credit_cards_helper.rb
```
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 +181,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 +192,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 +215,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
@@ -257,7 +256,7 @@ The generator checks that there exist the directories for models, controllers, h
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 +268,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 +282,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 +305,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 +313,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 +323,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 +332,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 +352,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,18 +372,18 @@ 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
Rack version 1.3
-Rails version 4.1.0
+Rails version 4.1.1
JavaScript Runtime Node.js (V8)
-Active Record version 4.1.0
-Action Pack version 4.1.0
-Action View version 4.1.0
-Action Mailer version 4.1.0
-Active Support version 4.1.0
+Active Record version 4.1.1
+Action Pack version 4.1.1
+Action View version 4.1.1
+Action Mailer version 4.1.1
+Active Support version 4.1.1
Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
Application root /home/foobar/commandsapp
Environment development
@@ -393,7 +393,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 +419,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 +439,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,9 +451,9 @@ 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:
+app/models/article.rb:
* [ 23] Have to fix this one before pushing!
```
@@ -458,7 +463,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 +535,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 805f4bb81f..a0f0738fba 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.
@@ -120,7 +120,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`.
-* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app.
+* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. NGINX or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app.
* `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified:
@@ -388,13 +388,13 @@ encrypted cookies salt value. Defaults to `'signed encrypted cookie'`.
* `config.action_view.embed_authenticity_token_in_remote_forms` allows you to set the default behavior for `authenticity_token` in forms with `:remote => true`. By default it's set to false, which means that remote forms will not include `authenticity_token`, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the `meta` tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass `:authenticity_token => true` as a form option or set this config setting to `true`
-* `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::PostsController` which renders this template:
+* `config.action_view.prefix_partial_path_with_controller_namespace` determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named `Admin::ArticlesController` which renders this template:
```erb
- <%= render @post %>
+ <%= render @article %>
```
- The default setting is `true`, which uses the partial at `/admin/posts/_post.erb`. Setting the value to `false` would render `/posts/_post.erb`, which is the same behavior as rendering from a non-namespaced controller such as `PostsController`.
+ The default setting is `true`, which uses the partial at `/admin/articles/_article.erb`. Setting the value to `false` would render `/articles/_article.erb`, which is the same behavior as rendering from a non-namespaced controller such as `ArticlesController`.
* `config.action_view.raise_on_missing_translations` determines whether an error should be raised for missing translations
@@ -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"}}
```
@@ -729,13 +729,47 @@ Rails will now prepend "/app1" when generating links.
#### Using Passenger
-Passenger makes it easy to run your application in a subdirectory. You can find
-the relevant configuration in the
-[passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri).
+Passenger makes it easy to run your application in a subdirectory. You can find the relevant configuration in the [Passenger manual](http://www.modrails.com/documentation/Users%20guide%20Apache.html#deploying_rails_to_sub_uri).
#### Using a Reverse Proxy
-TODO
+Deploying your application using a reverse proxy has definite advantages over traditional deploys. They allow you to have more control over your server by layering the components required by your application.
+
+Many modern web servers can be used as a proxy server to balance third-party elements such as caching servers or application servers.
+
+One such application server you can use is [Unicorn](http://unicorn.bogomips.org/) to run behind a reverse proxy.
+
+In this case, you would need to configure the proxy server (NGINX, Apache, etc) to accept connections from your application server (Unicorn). By default Unicorn will listen for TCP connections on port 8080, but you can change the port or configure it to use sockets instead.
+
+You can find more information in the [Unicorn readme](http://unicorn.bogomips.org/README.html) and understand the [philosophy](http://unicorn.bogomips.org/PHILOSOPHY.html) behind it.
+
+Once you've configured the application server, you must proxy requests to it by configuring your web server appropriately. For example your NGINX config may include:
+
+```
+upstream application_server {
+ server 0.0.0.0:8080
+}
+
+server {
+ listen 80;
+ server_name localhost;
+
+ root /root/path/to/your_app/public;
+
+ try_files $uri/index.html $uri.html @app;
+
+ location @app {
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header Host $http_host;
+ proxy_redirect off;
+ proxy_pass http://application_server;
+ }
+
+ # some other configuration
+}
+```
+
+Be sure to read the [NGINX documentation](http://nginx.org/en/docs/) for the most up-to-date information.
#### Considerations when deploying to a subdirectory
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 90c83a5e05..133ef58fd6 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.
@@ -144,7 +144,7 @@ WARNING: Docrails has a very strict policy: no code can be touched whatsoever, n
Contributing to the Rails Code
------------------------------
-### Setting Up a Development Environment ###
+### 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.
@@ -154,9 +154,9 @@ The easiest and recommended way to get a development environment ready to hack i
#### The Hard Way
-In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html).
+In case you can't use the Rails development box, see [this other guide](development_dependencies_install.html).
-### Clone the Rails Repository ###
+### Clone the Rails Repository
To be able to contribute code, you need to clone the Rails repository:
@@ -173,7 +173,7 @@ $ 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 ###
+### 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:
@@ -185,9 +185,9 @@ $ 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 ###
+### Write Your Code
-Now get busy and add/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.
@@ -215,7 +215,38 @@ Rails follows a simple set of coding style conventions:
The above are guidelines - please use your best judgment in using them.
-### Running Tests ###
+### Benchmark Your Code
+
+If your change has an impact on the performance of Rails, please use the
+[benchmark-ips](https://github.com/evanphx/benchmark-ips) gem to provide
+benchmark results for comparison.
+
+Here's an example of using benchmark-ips:
+
+```ruby
+require 'benchmark/ips'
+
+Benchmark.ips do |x|
+ x.report('addition') { 1 + 2 }
+ x.report('addition with send') { 1.send(:+, 2) }
+end
+```
+
+This will generate a report with the following information:
+
+```
+Calculating -------------------------------------
+ addition 69114 i/100ms
+ addition with send 64062 i/100ms
+-------------------------------------------------
+ addition 5307644.4 (±3.5%) i/s - 26539776 in 5.007219s
+ addition with send 3702897.9 (±3.5%) i/s - 18513918 in 5.006723s
+```
+
+Please see the benchmark/ips [README](https://github.com/evanphx/benchmark-ips/blob/master/README.md) for more information.
+
+### 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
@@ -228,35 +259,51 @@ tests are passing, that's enough to propose your contribution. We have
unexpected breakages elsewhere.
#### Entire Rails:
+
To run all the tests, do:
+
```bash
$ cd rails
$ bundle exec rake test
```
-#### Particular component of Rails
-To run tests only for particular component(ActionPack, ActiveRecord, etc.). For
-example, to run `ActionMailer` tests you can:
+
+#### 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
+$ 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
+test:mysql
+test:mysql2
+test:postgresql
```
Finally,
@@ -275,15 +322,7 @@ $ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.
You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.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.
-#### Single Test separately
-to run just one test. For example, to run `LayoutMailerTest` you can:
-
-```bash
-$ cd actionmailer
-$ ruby -w -Ilib:test test/mail_layout_test.rb
-```
-
-### Warnings ###
+### 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.
@@ -292,13 +331,14 @@ If you are sure about what you are doing and would like to have a more clear out
```bash
$ RUBYOPT=-W0 bundle exec rake test
```
-### Updating the CHANGELOG ###
+
+### 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
@@ -317,7 +357,7 @@ A CHANGELOG entry should summarize what was changed and should end with author's
Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph.
-### Sanity Check ###
+### Sanity Check
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
@@ -327,7 +367,7 @@ 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 ###
+### Commit Your Changes
When you're happy with the code on your computer, you need to commit the changes to Git:
@@ -351,9 +391,9 @@ it should not be necessary to visit a webpage to check the history.
Description can have multiple paragraphs and you can use code examples
inside, just indent it with 4 spaces:
- class PostsController
+ class ArticlesController
def index
- respond_with Post.limit(10)
+ respond_with Article.limit(10)
end
end
@@ -367,7 +407,7 @@ You can also add bullet points:
TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean.
-### Update Your Branch ###
+### Update Your Branch
It's pretty likely that other changes to master have happened while you were working. Go get them:
@@ -385,7 +425,7 @@ $ git rebase master
No conflicts? Tests still pass? Change still seems reasonable to you? Then move on.
-### Fork ###
+### Fork
Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and press "Fork" in the upper right hand corner.
@@ -475,11 +515,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
@@ -515,14 +555,13 @@ $ 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.
+### Older Versions of Ruby on Rails
-### 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:
+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 3-0-stable origin/3-0-stable
-$ git checkout 3-0-stable
+$ 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.
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index b067d9efb7..5f738b76af 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -26,17 +26,17 @@ One common task is to inspect the contents of a variable. In Rails, you can do t
The `debug` helper will return a \<pre> tag that renders the object using the YAML format. This will generate human-readable data from any object. For example, if you have this code in a view:
```html+erb
-<%= debug @post %>
+<%= debug @article %>
<p>
<b>Title:</b>
- <%= @post.title %>
+ <%= @article.title %>
</p>
```
You'll see something like this:
```yaml
---- !ruby/object:Post
+--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
@@ -55,10 +55,10 @@ Title: Rails debugging guide
Displaying an instance variable, or any other object or method, in YAML format can be achieved this way:
```html+erb
-<%= simple_format @post.to_yaml %>
+<%= simple_format @article.to_yaml %>
<p>
<b>Title:</b>
- <%= @post.title %>
+ <%= @article.title %>
</p>
```
@@ -67,7 +67,7 @@ The `to_yaml` method converts the method to YAML format leaving it more readable
As a result of this, you will have something like this in your view:
```yaml
---- !ruby/object:Post
+--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
@@ -88,7 +88,7 @@ Another useful method for displaying object values is `inspect`, especially when
<%= [1, 2, 3, 4, 5].inspect %>
<p>
<b>Title:</b>
- <%= @post.title %>
+ <%= @article.title %>
</p>
```
@@ -153,18 +153,18 @@ logger.fatal "Terminating application, raised unrecoverable error!!!"
Here's an example of a method instrumented with extra logging:
```ruby
-class PostsController < ApplicationController
+class ArticlesController < ApplicationController
# ...
def create
- @post = Post.new(params[:post])
- logger.debug "New post: #{@post.attributes.inspect}"
- logger.debug "Post should be valid: #{@post.valid?}"
-
- if @post.save
- flash[:notice] = 'Post was successfully created.'
- logger.debug "The post was saved and now the user is going to be redirected..."
- redirect_to(@post)
+ @article = Article.new(params[:article])
+ logger.debug "New article: #{@article.attributes.inspect}"
+ logger.debug Article should be valid: #{@article.valid?}"
+
+ if @article.save
+ flash[:notice] = Article was successfully created.'
+ logger.debug "The article was saved and now the user is going to be redirected..."
+ redirect_to(@article)
else
render action: "new"
end
@@ -177,21 +177,20 @@ end
Here's an example of the log generated when this controller action is executed:
```
-Processing PostsController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
+Processing ArticlesController#create (for 127.0.0.1 at 2008-09-08 11:52:54) [POST]
Session ID: BAh7BzoMY3NyZl9pZCIlMDY5MWU1M2I1ZDRjODBlMzkyMWI1OTg2NWQyNzViZjYiCmZsYXNoSUM6J0FjdGl
vbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhhc2h7AAY6CkB1c2VkewA=--b18cd92fba90eacf8137e5f6b3b06c4d724596a4
- Parameters: {"commit"=>"Create", "post"=>{"title"=>"Debugging Rails",
+ Parameters: {"commit"=>"Create", "article"=>{"title"=>"Debugging Rails",
"body"=>"I'm learning how to print in logs!!!", "published"=>"0"},
- "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"posts"}
-New post: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!",
- "published"=>false, "created_at"=>nil}
-Post should be valid: true
- Post Create (0.000443) INSERT INTO "posts" ("updated_at", "title", "body", "published",
+ "authenticity_token"=>"2059c1286e93402e389127b1153204e0d1e275dd", "action"=>"create", "controller"=>"articles"}
+New article: {"updated_at"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!",
+ "published"=>false, "created_at"=>nil} Article should be valid: true
+ Article Create (0.000443) INSERT INTO "articles" ("updated_at", "title", "body", "published",
"created_at") VALUES('2008-09-08 14:52:54', 'Debugging Rails',
'I''m learning how to print in logs!!!', 'f', '2008-09-08 14:52:54')
-The post was saved and now the user is going to be redirected...
-Redirected to #<Post:0x20af760>
-Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/posts]
+The article was saved and now the user is going to be redirected...
+Redirected to # Article:0x20af760>
+Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localhost/articles]
```
Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels to avoid filling your production logs with useless trivia.
@@ -286,17 +285,17 @@ 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:
```
-[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
+[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
3:
- 4: # GET /posts
- 5: # GET /posts.json
+ 4: # GET /articles
+ 5: # GET /articles.json
6: def index
7: byebug
-=> 8: @posts = Post.find_recent
+=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
- 12: format.json { render json: @posts }
+ 12: format.json { render json: @articles }
(byebug)
```
@@ -309,7 +308,7 @@ For example:
```bash
=> Booting WEBrick
-=> Rails 4.1.0 application starting in development on http://0.0.0.0:3000
+=> Rails 4.1.1 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
@@ -320,19 +319,19 @@ For example:
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
+Processing by ArticlesController#index as HTML
-[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
3:
- 4: # GET /posts
- 5: # GET /posts.json
+ 4: # GET /articles
+ 5: # GET /articles.json
6: def index
7: byebug
-=> 8: @posts = Post.find_recent
+=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
- 12: format.json { render json: @posts }
+ 12: format.json { render json: @articles }
(byebug)
```
@@ -365,15 +364,15 @@ To see the previous ten lines you should type `list-` (or `l-`)
```
(byebug) l-
-[1, 10] in /PathTo/project/app/controllers/posts_controller.rb
- 1 class PostsController < ApplicationController
- 2 before_action :set_post, only: [:show, :edit, :update, :destroy]
+[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
+ 1 class ArticlesController < ApplicationController
+ 2 before_action :set_article, only: [:show, :edit, :update, :destroy]
3
- 4 # GET /posts
- 5 # GET /posts.json
+ 4 # GET /articles
+ 5 # GET /articles.json
6 def index
7 byebug
- 8 @posts = Post.find_recent
+ 8 @articles = Article.find_recent
9
10 respond_to do |format|
@@ -386,17 +385,17 @@ the code again you can type `list=`
```
(byebug) list=
-[3, 12] in /PathTo/project/app/controllers/posts_controller.rb
+[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
3:
- 4: # GET /posts
- 5: # GET /posts.json
+ 4: # GET /articles
+ 5: # GET /articles.json
6: def index
7: byebug
-=> 8: @posts = Post.find_recent
+=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
- 12: format.json { render json: @posts }
+ 12: format.json { render json: @articles }
(byebug)
```
@@ -419,14 +418,14 @@ then `backtrace` will supply the answer.
```
(byebug) where
---> #0 PostsController.index
- at /PathTo/project/test_app/app/controllers/posts_controller.rb:8
+--> #0 ArticlesController.index
+ at /PathTo/project/test_app/app/controllers/articles_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
+ at /PathToGems/actionpack-4.1.1/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
+ at /PathToGems/actionpack-4.1.1/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
+ at /PathToGems/actionpack-4.1.1/lib/action_controller/metal/rendering.rb:10
...
```
@@ -438,7 +437,7 @@ context.
```
(byebug) frame 2
-[184, 193] in /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb
+[184, 193] in /PathToGems/actionpack-4.1.1/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
@@ -487,17 +486,17 @@ 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, 12] in /PathTo/project/app/controllers/articles_controller.rb
3:
- 4: # GET /posts
- 5: # GET /posts.json
+ 4: # GET /articles
+ 5: # GET /articles.json
6: def index
7: byebug
-=> 8: @posts = Post.find_recent
+=> 8: @articles = Article.find_recent
9:
10: respond_to do |format|
11: format.html # index.html.erb
- 12: format.json { render json: @posts }
+ 12: format.json { render json: @articles }
(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request,
@@ -512,15 +511,15 @@ command later in this guide).
```
(byebug) next
-[5, 14] in /PathTo/project/app/controllers/posts_controller.rb
- 5 # GET /posts.json
+[5, 14] in /PathTo/project/app/controllers/articles_controller.rb
+ 5 # GET /articles.json
6 def index
7 byebug
- 8 @posts = Post.find_recent
+ 8 @articles = Article.find_recent
9
=> 10 respond_to do |format|
11 format.html # index.html.erb
- 12 format.json { render json: @posts }
+ 12 format.json { render json: @articles }
13 end
14 end
15
@@ -530,11 +529,11 @@ command later in this guide).
And then ask again for the instance_variables:
```
-(byebug) instance_variables.include? "@posts"
+(byebug) instance_variables.include? "@articles"
true
```
-Now `@posts` is included in the instance variables, because the line defining it
+Now `@articles` 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!).
@@ -564,7 +563,7 @@ example, to check that we have no local variables currently defined.
You can also inspect for an object method this way:
```
-(byebug) var instance Post.new
+(byebug) var instance Article.new
@_start_transaction_state = {}
@aggregation_cache = {}
@association_cache = {}
@@ -581,8 +580,8 @@ 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.
```
-(byebug) display @posts
-1: @posts = nil
+(byebug) display @articles
+1: @articles = nil
```
The variables inside the displaying list will be printed with their values after
@@ -611,10 +610,10 @@ For example, consider the following situation:
```ruby
Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200
-Processing by PostsController#index as HTML
+Processing by ArticlesController#index as HTML
-[1, 8] in /home/davidr/Proyectos/test_app/app/models/post.rb
- 1: class Post < ActiveRecord::Base
+[1, 8] in /home/davidr/Proyectos/test_app/app/models/article.rb
+ 1: class Article < ActiveRecord::Base
2:
3: def self.find_recent(limit = 10)
4: byebug
@@ -634,15 +633,15 @@ the method, so `byebug` will jump to next next line of the previous frame.
(byebug) next
Next went up a frame because previous frame finished
-[4, 13] in /PathTo/project/test_app/app/controllers/posts_controller.rb
- 4: # GET /posts
- 5: # GET /posts.json
+[4, 13] in /PathTo/project/test_app/app/controllers/articles_controller.rb
+ 4: # GET /articles
+ 5: # GET /articles.json
6: def index
- 7: @posts = Post.find_recent
+ 7: @articles = Article.find_recent
8:
=> 9: respond_to do |format|
10: format.html # index.html.erb
- 11: format.json { render json: @posts }
+ 11: format.json { render json: @articles }
12: end
13: end
@@ -655,7 +654,7 @@ instruction to be executed. In this case, the activesupport's `week` method.
```
(byebug) step
-[50, 59] in /PathToGems/activesupport-4.1.0/lib/active_support/core_ext/numeric/time.rb
+[50, 59] in /PathToGems/activesupport-4.1.1/lib/active_support/core_ext/numeric/time.rb
50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
51: end
52: alias :day :days
@@ -693,20 +692,20 @@ _expression_ works the same way as with file:line.
For example, in the previous situation
```
-[4, 13] in /PathTo/project/app/controllers/posts_controller.rb
- 4: # GET /posts
- 5: # GET /posts.json
+[4, 13] in /PathTo/project/app/controllers/articles_controller.rb
+ 4: # GET /articles
+ 5: # GET /articles.json
6: def index
- 7: @posts = Post.find_recent
+ 7: @articles = Article.find_recent
8:
=> 9: respond_to do |format|
10: format.html # index.html.erb
- 11: format.json { render json: @posts }
+ 11: format.json { render json: @articles }
12: end
13: end
(byebug) break 11
-Created breakpoint 1 at /PathTo/project/app/controllers/posts_controller.rb:11
+Created breakpoint 1 at /PathTo/project/app/controllers/articles_controller.rb:11
```
@@ -716,7 +715,7 @@ supply a number, it lists that breakpoint. Otherwise it lists all breakpoints.
```
(byebug) info breakpoints
Num Enb What
-1 y at /PathTo/project/app/controllers/posts_controller.rb:11
+1 y at /PathTo/project/app/controllers/articles_controller.rb:11
```
To delete breakpoints: use the command `delete _n_` to remove the breakpoint
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index b0e070120d..b134c9d2d0 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -249,7 +249,7 @@ 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
@@ -267,7 +267,7 @@ 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/documents.yaml b/guides/source/documents.yaml
index a160c462b2..82e248ee38 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -13,8 +13,8 @@
url: active_record_basics.html
description: This guide will get you started with models, persistence to database and the Active Record pattern and library.
-
- name: Rails Database Migrations
- url: migrations.html
+ name: Active Record Migrations
+ url: active_record_migrations.html
description: This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner.
-
name: Active Record Validations
@@ -96,11 +96,6 @@
url: command_line.html
description: This guide covers the command line tools and rake tasks provided by Rails.
-
- name: Caching with Rails
- work_in_progress: true
- url: caching_with_rails.html
- description: Various caching techniques provided by Rails.
- -
name: Asset Pipeline
url: asset_pipeline.html
description: This guide documents the asset pipeline.
@@ -162,7 +157,6 @@
-
name: Upgrading Ruby on Rails
url: upgrading_ruby_on_rails.html
- work_in_progress: true
description: This guide helps in upgrading applications to latest Ruby on Rails versions.
-
name: Ruby on Rails 4.1 Release Notes
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 8f9ba0995f..e7f024f1fc 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -36,14 +36,14 @@ engine **can** be a plugin, and a plugin **can** be an engine.
The engine that will be created in this guide will be called "blorgh". The
engine will provide blogging functionality to its host applications, allowing
-for new posts and comments to be created. At the beginning of this guide, you
+for new articles and comments to be created. At the beginning of this guide, you
will be working solely within the engine itself, but in later sections you'll
see how to hook it into an application.
Engines can also be isolated from their host applications. This means that an
application is able to have a path provided by a routing helper such as
-`posts_path` and use an engine also that provides a path also called
-`posts_path`, and the two would not clash. Along with this, controllers, models
+`articles_path` and use an engine also that provides a path also called
+`articles_path`, and the two would not clash. Along with this, controllers, models
and table names are also namespaced. You'll see how to do this later in this
guide.
@@ -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,12 +197,12 @@ 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
-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
-`Blorgh::PostsController` and the views for that controller will not be at
-`app/views/posts`, but `app/views/blorgh/posts` instead. Mailers are namespaced
+to `bin/rails g model`, such as `bin/rails g model article`, won't be called `Article`, but
+instead be namespaced and called `Blorgh::Article`. In addition, the table for the
+model is namespaced, becoming `blorgh_articles`, rather than simply `articles`.
+Similar to the model namespacing, a controller called `ArticlesController` becomes
+`Blorgh::ArticlesController` and the views for that controller will not be at
+`app/views/articles`, but `app/views/blorgh/articles` instead. Mailers are namespaced
as well.
Finally, routes will also be isolated within the engine. This is one of the most
@@ -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
@@ -283,74 +283,74 @@ created in the `test` directory as well. For example, you may wish to create a
Providing engine functionality
------------------------------
-The engine that this guide covers provides posting and commenting functionality
-and follows a similar thread to the [Getting Started
+The engine that this guide covers provides submitting articles and commenting
+functionality and follows a similar thread to the [Getting Started
Guide](getting_started.html), with some new twists.
-### Generating a Post Resource
+### Generating an Article Resource
-The first thing to generate for a blog engine is the `Post` model and related
+The first thing to generate for a blog engine is the `Article` 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 article title:string text:text
```
This command will output this information:
```
invoke active_record
-create db/migrate/[timestamp]_create_blorgh_posts.rb
-create app/models/blorgh/post.rb
+create db/migrate/[timestamp]_create_blorgh_articles.rb
+create app/models/blorgh/article.rb
invoke test_unit
-create test/models/blorgh/post_test.rb
-create test/fixtures/blorgh/posts.yml
+create test/models/blorgh/article_test.rb
+create test/fixtures/blorgh/articles.yml
invoke resource_route
- route resources :posts
+ route resources :articles
invoke scaffold_controller
-create app/controllers/blorgh/posts_controller.rb
+create app/controllers/blorgh/articles_controller.rb
invoke erb
-create app/views/blorgh/posts
-create app/views/blorgh/posts/index.html.erb
-create app/views/blorgh/posts/edit.html.erb
-create app/views/blorgh/posts/show.html.erb
-create app/views/blorgh/posts/new.html.erb
-create app/views/blorgh/posts/_form.html.erb
+create app/views/blorgh/articles
+create app/views/blorgh/articles/index.html.erb
+create app/views/blorgh/articles/edit.html.erb
+create app/views/blorgh/articles/show.html.erb
+create app/views/blorgh/articles/new.html.erb
+create app/views/blorgh/articles/_form.html.erb
invoke test_unit
-create test/controllers/blorgh/posts_controller_test.rb
+create test/controllers/blorgh/articles_controller_test.rb
invoke helper
-create app/helpers/blorgh/posts_helper.rb
+create app/helpers/blorgh/articles_helper.rb
invoke test_unit
-create test/helpers/blorgh/posts_helper_test.rb
+create test/helpers/blorgh/articles_helper_test.rb
invoke assets
invoke js
-create app/assets/javascripts/blorgh/posts.js
+create app/assets/javascripts/blorgh/articles.js
invoke css
-create app/assets/stylesheets/blorgh/posts.css
+create app/assets/stylesheets/blorgh/articles.css
invoke css
create app/assets/stylesheets/scaffold.css
```
The first thing that the scaffold generator does is invoke the `active_record`
generator, which generates a migration and a model for the resource. Note here,
-however, that the migration is called `create_blorgh_posts` rather than the
-usual `create_posts`. This is due to the `isolate_namespace` method called in
+however, that the migration is called `create_blorgh_articles` rather than the
+usual `create_articles`. This is due to the `isolate_namespace` method called in
the `Blorgh::Engine` class's definition. The model here is also namespaced,
-being placed at `app/models/blorgh/post.rb` rather than `app/models/post.rb` due
+being placed at `app/models/blorgh/article.rb` rather than `app/models/article.rb` due
to the `isolate_namespace` call within the `Engine` class.
Next, the `test_unit` generator is invoked for this model, generating a model
-test at `test/models/blorgh/post_test.rb` (rather than
-`test/models/post_test.rb`) and a fixture at `test/fixtures/blorgh/posts.yml`
-(rather than `test/fixtures/posts.yml`).
+test at `test/models/blorgh/article_test.rb` (rather than
+`test/models/article_test.rb`) and a fixture at `test/fixtures/blorgh/articles.yml`
+(rather than `test/fixtures/articles.yml`).
After that, a line for the resource is inserted into the `config/routes.rb` file
-for the engine. This line is simply `resources :posts`, turning the
+for the engine. This line is simply `resources :articles`, turning the
`config/routes.rb` file for the engine into this:
```ruby
Blorgh::Engine.routes.draw do
- resources :posts
+ resources :articles
end
```
@@ -362,18 +362,18 @@ be isolated from those routes that are within the application. The
[Routes](#routes) section of this guide describes it in detail.
Next, the `scaffold_controller` generator is invoked, generating a controller
-called `Blorgh::PostsController` (at
-`app/controllers/blorgh/posts_controller.rb`) and its related views at
-`app/views/blorgh/posts`. This generator also generates a test for the
-controller (`test/controllers/blorgh/posts_controller_test.rb`) and a helper
-(`app/helpers/blorgh/posts_controller.rb`).
+called `Blorgh::ArticlesController` (at
+`app/controllers/blorgh/articles_controller.rb`) and its related views at
+`app/views/blorgh/articles`. This generator also generates a test for the
+controller (`test/controllers/blorgh/articles_controller_test.rb`) and a helper
+(`app/helpers/blorgh/articles_controller.rb`).
Everything this generator has created is neatly namespaced. The controller's
class is defined within the `Blorgh` module:
```ruby
module Blorgh
- class PostsController < ApplicationController
+ class ArticlesController < ApplicationController
...
end
end
@@ -382,22 +382,22 @@ end
NOTE: The `ApplicationController` class being inherited from here is the
`Blorgh::ApplicationController`, not an application's `ApplicationController`.
-The helper inside `app/helpers/blorgh/posts_helper.rb` is also namespaced:
+The helper inside `app/helpers/blorgh/articles_helper.rb` is also namespaced:
```ruby
module Blorgh
- module PostsHelper
+ module ArticlesHelper
...
end
end
```
This helps prevent conflicts with any other engine or application that may have
-a post resource as well.
+a article resource as well.
Finally, the assets for this resource are generated in two files:
-`app/assets/javascripts/blorgh/posts.js` and
-`app/assets/stylesheets/blorgh/posts.css`. You'll see how to use these a little
+`app/assets/javascripts/blorgh/articles.js` and
+`app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little
later.
By default, the scaffold styling is not applied to the engine because the
@@ -412,46 +412,46 @@ tag of this layout:
You can see what the engine has so far by running `rake db:migrate` at the root
of our engine to run the migration generated by the scaffold generator, and then
running `rails server` in `test/dummy`. When you open
-`http://localhost:3000/blorgh/posts` you will see the default scaffold that has
+`http://localhost:3000/blorgh/articles` you will see the default scaffold that has
been generated. Click around! You've just generated your first engine's first
functions.
If you'd rather play around in the console, `rails console` will also work just
-like a Rails application. Remember: the `Post` model is namespaced, so to
-reference it you must call it as `Blorgh::Post`.
+like a Rails application. Remember: the `Article` model is namespaced, so to
+reference it you must call it as `Blorgh::Article`.
```ruby
->> Blorgh::Post.find(1)
-=> #<Blorgh::Post id: 1 ...>
+>> Blorgh::Article.find(1)
+=> #<Blorgh::Article id: 1 ...>
```
-One final thing is that the `posts` resource for this engine should be the root
+One final thing is that the `articles` resource for this engine should be the root
of the engine. Whenever someone goes to the root path where the engine is
-mounted, they should be shown a list of posts. This can be made to happen if
+mounted, they should be shown a list of articles. This can be made to happen if
this line is inserted into the `config/routes.rb` file inside the engine:
```ruby
-root to: "posts#index"
+root to: "articles#index"
```
-Now people will only need to go to the root of the engine to see all the posts,
-rather than visiting `/posts`. This means that instead of
-`http://localhost:3000/blorgh/posts`, you only need to go to
+Now people will only need to go to the root of the engine to see all the articles,
+rather than visiting `/articles`. This means that instead of
+`http://localhost:3000/blorgh/articles`, you only need to go to
`http://localhost:3000/blorgh` now.
### Generating a Comments Resource
-Now that the engine can create new blog posts, it only makes sense to add
+Now that the engine can create new articles, it only makes sense to add
commenting functionality as well. To do this, you'll need to generate a comment
-model, a comment controller and then modify the posts scaffold to display
+model, a comment controller and then modify the articles scaffold to display
comments and allow people to create new ones.
From the application root, run the model generator. Tell it to generate a
-`Comment` model, with the related table having two columns: a `post_id` integer
+`Comment` model, with the related table having two columns: a `article_id` integer
and `text` text column.
```bash
-$ rails generate model Comment post_id:integer text:text
+$ bin/rails generate model Comment article_id:integer text:text
```
This will output the following:
@@ -474,17 +474,17 @@ table:
$ rake db:migrate
```
-To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and
+To show the comments on an article, edit `app/views/blorgh/articles/show.html.erb` and
add this line before the "Edit" link:
```html+erb
<h3>Comments</h3>
-<%= render @post.comments %>
+<%= render @article.comments %>
```
This line will require there to be a `has_many` association for comments defined
-on the `Blorgh::Post` model, which there isn't right now. To define one, open
-`app/models/blorgh/post.rb` and add this line into the model:
+on the `Blorgh::Article` model, which there isn't right now. To define one, open
+`app/models/blorgh/article.rb` and add this line into the model:
```ruby
has_many :comments
@@ -494,7 +494,7 @@ Turning the model into this:
```ruby
module Blorgh
- class Post < ActiveRecord::Base
+ class Article < ActiveRecord::Base
has_many :comments
end
end
@@ -505,9 +505,9 @@ NOTE: Because the `has_many` is defined inside a class that is inside the
model for these objects, so there's no need to specify that using the
`:class_name` option here.
-Next, there needs to be a form so that comments can be created on a post. To add
-this, put this line underneath the call to `render @post.comments` in
-`app/views/blorgh/posts/show.html.erb`:
+Next, there needs to be a form so that comments can be created on a article. To add
+this, put this line underneath the call to `render @article.comments` in
+`app/views/blorgh/articles/show.html.erb`:
```erb
<%= render "blorgh/comments/form" %>
@@ -519,7 +519,7 @@ directory at `app/views/blorgh/comments` and in it a new file called
```html+erb
<h3>New comment</h3>
-<%= form_for [@post, @post.comments.build] do |f| %>
+<%= form_for [@article, @article.comments.build] do |f| %>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
@@ -529,12 +529,12 @@ directory at `app/views/blorgh/comments` and in it a new file called
```
When this form is submitted, it is going to attempt to perform a `POST` request
-to a route of `/posts/:post_id/comments` within the engine. This route doesn't
-exist at the moment, but can be created by changing the `resources :posts` line
+to a route of `/articles/:article_id/comments` within the engine. This route doesn't
+exist at the moment, but can be created by changing the `resources :articles` line
inside `config/routes.rb` into these lines:
```ruby
-resources :posts do
+resources :articles do
resources :comments
end
```
@@ -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:
@@ -567,17 +567,17 @@ invoke css
create app/assets/stylesheets/blorgh/comments.css
```
-The form will be making a `POST` request to `/posts/:post_id/comments`, which
+The form will be making a `POST` request to `/articles/:article_id/comments`, which
will correspond with the `create` action in `Blorgh::CommentsController`. This
action needs to be created, which can be done by putting the following lines
inside the class definition in `app/controllers/blorgh/comments_controller.rb`:
```ruby
def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.create(comment_params)
+ @article = Article.find(params[:article_id])
+ @comment = @article.comments.create(comment_params)
flash[:notice] = "Comment has been created!"
- redirect_to posts_path
+ redirect_to articles_path
end
private
@@ -590,11 +590,11 @@ This is the final step required to get the new comment form working. Displaying
the comments, however, is not quite right yet. If you were to create a comment
right now, you would see this error:
-```
+```
Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder],
:formats=>[:html], :locale=>[:en, :en]}. Searched in: *
"/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views" *
-"/Users/ryan/Sites/side_projects/blorgh/app/views"
+"/Users/ryan/Sites/side_projects/blorgh/app/views"
```
The engine is unable to find the partial required for rendering the comments.
@@ -612,7 +612,7 @@ line inside it:
```
The `comment_counter` local variable is given to us by the `<%= render
-@post.comments %>` call, which will define it automatically and increment the
+@article.comments %>` call, which will define it automatically and increment the
counter as it iterates through each comment. It's used in this example to
display a small number next to each comment when it's created.
@@ -625,7 +625,7 @@ Hooking Into an Application
Using an engine within an application is very easy. This section covers how to
mount the engine into an application and the initial setup required, as well as
linking the engine to a `User` class provided by the application to provide
-ownership for posts and comments within the engine.
+ownership for articles and comments within the engine.
### Mounting the Engine
@@ -676,7 +676,7 @@ pre-defined path which may be customizable.
### Engine setup
-The engine contains migrations for the `blorgh_posts` and `blorgh_comments`
+The engine contains migrations for the `blorgh_articles` and `blorgh_comments`
table which need to be created in the application's database so that the
engine's models can query them correctly. To copy these migrations into the
application use this command:
@@ -698,7 +698,7 @@ haven't been copied over already. The first run for this command will output
something such as this:
```bash
-Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh
+Copied migration [timestamp_1]_create_blorgh_articles.rb from blorgh
Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh
```
@@ -709,7 +709,7 @@ migrations in the application.
To run these migrations within the context of the application, simply run `rake
db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the
-posts will be empty. This is because the table created inside the application is
+articles will be empty. This is because the table created inside the application is
different from the one created within the engine. Go ahead, play around with the
newly mounted engine. You'll find that it's the same as when it was only an
engine.
@@ -734,11 +734,11 @@ rake db:migrate SCOPE=blorgh VERSION=0
When an engine is created, it may want to use specific classes from an
application to provide links between the pieces of the engine and the pieces of
-the application. In the case of the `blorgh` engine, making posts and comments
+the application. In the case of the `blorgh` engine, making articles and comments
have authors would make a lot of sense.
A typical application might have a `User` class that would be used to represent
-authors for a post or a comment. But there could be a case where the application
+authors for a article or a comment. But there could be a case where the application
calls this class something different, such as `Person`. For this reason, the
engine should not hardcode associations specifically for a `User` class.
@@ -753,14 +753,14 @@ rails g model user name:string
The `rake db:migrate` command needs to be run here to ensure that our
application has the `users` table for future use.
-Also, to keep it simple, the posts form will have a new text field called
+Also, to keep it simple, the articles form will have a new text field called
`author_name`, where users can elect to put their name. The engine will then
take this name and either create a new `User` object from it, or find one that
-already has that name. The engine will then associate the post with the found or
+already has that name. The engine will then associate the article with the found or
created `User` object.
First, the `author_name` text field needs to be added to the
-`app/views/blorgh/posts/_form.html.erb` partial inside the engine. This can be
+`app/views/blorgh/articles/_form.html.erb` partial inside the engine. This can be
added above the `title` field with this code:
```html+erb
@@ -770,23 +770,23 @@ added above the `title` field with this code:
</div>
```
-Next, we need to update our `Blorgh::PostController#post_params` method to
+Next, we need to update our `Blorgh::ArticleController#article_params` method to
permit the new form parameter:
```ruby
-def post_params
- params.require(:post).permit(:title, :text, :author_name)
+def article_params
+ params.require(:article).permit(:title, :text, :author_name)
end
```
-The `Blorgh::Post` model should then have some code to convert the `author_name`
-field into an actual `User` object and associate it as that post's `author`
-before the post is saved. It will also need to have an `attr_accessor` set up
+The `Blorgh::Article` model should then have some code to convert the `author_name`
+field into an actual `User` object and associate it as that article's `author`
+before the article is saved. It will also need to have an `attr_accessor` set up
for this field, so that the setter and getter methods are defined for it.
To do all this, you'll need to add the `attr_accessor` for `author_name`, the
association for the author and the `before_save` call into
-`app/models/blorgh/post.rb`. The `author` association will be hard-coded to the
+`app/models/blorgh/article.rb`. The `author` association will be hard-coded to the
`User` class for the time being.
```ruby
@@ -803,14 +803,14 @@ private
By representing the `author` association's object with the `User` class, a link
is established between the engine and the application. There needs to be a way
-of associating the records in the `blorgh_posts` table with the records in the
+of associating the records in the `blorgh_articles` table with the records in the
`users` table. Because the association is called `author`, there should be an
-`author_id` column added to the `blorgh_posts` table.
+`author_id` column added to the `blorgh_articles` table.
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_articles author_id:integer
```
NOTE: Due to the migration's name and the column specification after it, Rails
@@ -828,12 +828,12 @@ $ rake blorgh:install:migrations
Notice that only _one_ migration was copied over here. This is because the first
two migrations were copied over the first time this command was run.
-```
-NOTE Migration [timestamp]_create_blorgh_posts.rb from blorgh has been
+```
+NOTE Migration [timestamp]_create_blorgh_articles.rb from blorgh has been
skipped. Migration with the same name already exists. NOTE Migration
[timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration
with the same name already exists. Copied migration
-[timestamp]_add_author_id_to_blorgh_posts.rb from blorgh
+[timestamp]_add_author_id_to_blorgh_articles.rb from blorgh
```
Run the migration using:
@@ -843,20 +843,20 @@ $ rake db:migrate
```
Now with all the pieces in place, an action will take place that will associate
-an author - represented by a record in the `users` table - with a post,
-represented by the `blorgh_posts` table from the engine.
+an author - represented by a record in the `users` table - with an article,
+represented by the `blorgh_articles` table from the engine.
-Finally, the author's name should be displayed on the post's page. Add this code
-above the "Title" output inside `app/views/blorgh/posts/show.html.erb`:
+Finally, the author's name should be displayed on the article's page. Add this code
+above the "Title" output inside `app/views/blorgh/articles/show.html.erb`:
```html+erb
<p>
<b>Author:</b>
- <%= @post.author %>
+ <%= @article.author %>
</p>
```
-By outputting `@post.author` using the `<%=` tag, the `to_s` method will be
+By outputting `@article.author` using the `<%=` tag, the `to_s` method will be
called on the object. By default, this will look quite ugly:
```
@@ -925,15 +925,15 @@ This method works like its brothers, `attr_accessor` and `cattr_accessor`, but
provides a setter and getter method on the module with the specified name. To
use it, it must be referenced using `Blorgh.author_class`.
-The next step is to switch the `Blorgh::Post` model over to this new setting.
+The next step is to switch the `Blorgh::Article` model over to this new setting.
Change the `belongs_to` association inside this model
-(`app/models/blorgh/post.rb`) to this:
+(`app/models/blorgh/article.rb`) to this:
```ruby
belongs_to :author, class_name: Blorgh.author_class
```
-The `set_author` method in the `Blorgh::Post` model should also use this class:
+The `set_author` method in the `Blorgh::Article` model should also use this class:
```ruby
self.author = Blorgh.author_class.constantize.find_or_create_by(name: author_name)
@@ -960,7 +960,7 @@ Resulting in something a little shorter, and more implicit in its behavior. The
`author_class` method should always return a `Class` object.
Since we changed the `author_class` method to return a `Class` instead of a
-`String`, we must also modify our `belongs_to` definition in the `Blorgh::Post`
+`String`, we must also modify our `belongs_to` definition in the `Blorgh::Article`
model:
```ruby
@@ -985,14 +985,14 @@ to load that class and then reference the related table. This could lead to
problems if the table wasn't already existing. Therefore, a `String` should be
used and then converted to a class using `constantize` in the engine later on.
-Go ahead and try to create a new post. You will see that it works exactly in the
+Go ahead and try to create a new article. You will see that it works exactly in the
same way as before, except this time the engine is using the configuration
setting in `config/initializers/blorgh.rb` to learn what the class is.
There are now no strict dependencies on what the class is, only what the API for
the class must be. The engine simply requires this class to define a
`find_or_create_by` method which returns an object of that class, to be
-associated with a post when it's created. This object, of course, should have
+associated with an article when it's created. This object, of course, should have
some sort of identifier by which it can be referenced.
#### General Engine Configuration
@@ -1107,12 +1107,12 @@ that isn't referenced by your main application.
#### Implementing Decorator Pattern Using Class#class_eval
-**Adding** `Post#time_since_created`:
+**Adding** `Article#time_since_created`:
```ruby
-# MyApp/app/decorators/models/blorgh/post_decorator.rb
+# MyApp/app/decorators/models/blorgh/article_decorator.rb
-Blorgh::Post.class_eval do
+Blorgh::Article.class_eval do
def time_since_created
Time.current - created_at
end
@@ -1120,20 +1120,20 @@ end
```
```ruby
-# Blorgh/app/models/post.rb
+# Blorgh/app/models/article.rb
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
has_many :comments
end
```
-**Overriding** `Post#summary`:
+**Overriding** `Article#summary`:
```ruby
-# MyApp/app/decorators/models/blorgh/post_decorator.rb
+# MyApp/app/decorators/models/blorgh/article_decorator.rb
-Blorgh::Post.class_eval do
+Blorgh::Article.class_eval do
def summary
"#{title} - #{truncate(text)}"
end
@@ -1141,9 +1141,9 @@ end
```
```ruby
-# Blorgh/app/models/post.rb
+# Blorgh/app/models/article.rb
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
has_many :comments
def summary
"#{title}"
@@ -1159,13 +1159,13 @@ class modifications, you might want to consider using [`ActiveSupport::Concern`]
ActiveSupport::Concern manages load order of interlinked dependent modules and
classes at run time allowing you to significantly modularize your code.
-**Adding** `Post#time_since_created` and **Overriding** `Post#summary`:
+**Adding** `Article#time_since_created` and **Overriding** `Article#summary`:
```ruby
-# MyApp/app/models/blorgh/post.rb
+# MyApp/app/models/blorgh/article.rb
-class Blorgh::Post < ActiveRecord::Base
- include Blorgh::Concerns::Models::Post
+class Blorgh::Article < ActiveRecord::Base
+ include Blorgh::Concerns::Models::Article
def time_since_created
Time.current - created_at
@@ -1178,22 +1178,22 @@ end
```
```ruby
-# Blorgh/app/models/post.rb
+# Blorgh/app/models/article.rb
-class Post < ActiveRecord::Base
- include Blorgh::Concerns::Models::Post
+class Article < ActiveRecord::Base
+ include Blorgh::Concerns::Models::Article
end
```
```ruby
-# Blorgh/lib/concerns/models/post
+# Blorgh/lib/concerns/models/article
-module Blorgh::Concerns::Models::Post
+module Blorgh::Concerns::Models::Article
extend ActiveSupport::Concern
# 'included do' causes the included code to be evaluated in the
- # context where it is included (post.rb), rather than being
- # executed in the module's context (blorgh/concerns/models/post).
+ # context where it is included (article.rb), rather than being
+ # executed in the module's context (blorgh/concerns/models/article).
included do
attr_accessor :author_name
belongs_to :author, class_name: "User"
@@ -1224,25 +1224,25 @@ When Rails looks for a view to render, it will first look in the `app/views`
directory of the application. If it cannot find the view there, it will check in
the `app/views` directories of all engines that have this directory.
-When the application is asked to render the view for `Blorgh::PostsController`'s
+When the application is asked to render the view for `Blorgh::ArticlesController`'s
index action, it will first look for the path
-`app/views/blorgh/posts/index.html.erb` within the application. If it cannot
+`app/views/blorgh/articles/index.html.erb` within the application. If it cannot
find it, it will look inside the engine.
You can override this view in the application by simply creating a new file at
-`app/views/blorgh/posts/index.html.erb`. Then you can completely change what
+`app/views/blorgh/articles/index.html.erb`. Then you can completely change what
this view would normally output.
-Try this now by creating a new file at `app/views/blorgh/posts/index.html.erb`
+Try this now by creating a new file at `app/views/blorgh/articles/index.html.erb`
and put this content in it:
```html+erb
-<h1>Posts</h1>
-<%= link_to "New Post", new_post_path %>
-<% @posts.each do |post| %>
- <h2><%= post.title %></h2>
- <small>By <%= post.author %></small>
- <%= simple_format(post.text) %>
+<h1>Articles</h1>
+<%= link_to "New Article", new_article_path %>
+<% @articles.each do |article| %>
+ <h2><%= article.title %></h2>
+ <small>By <%= article.author %></small>
+ <%= simple_format(article.text) %>
<hr>
<% end %>
```
@@ -1259,30 +1259,30 @@ Routes inside an engine are drawn on the `Engine` class within
```ruby
Blorgh::Engine.routes.draw do
- resources :posts
+ resources :articles
end
```
By having isolated routes such as this, if you wish to link to an area of an
engine from within an application, you will need to use the engine's routing
-proxy method. Calls to normal routing methods such as `posts_path` may end up
+proxy method. Calls to normal routing methods such as `articles_path` may end up
going to undesired locations if both the application and the engine have such a
helper defined.
-For instance, the following example would go to the application's `posts_path`
-if that template was rendered from the application, or the engine's `posts_path`
+For instance, the following example would go to the application's `articles_path`
+if that template was rendered from the application, or the engine's `articles_path`
if it was rendered from the engine:
```erb
-<%= link_to "Blog posts", posts_path %>
+<%= link_to "Blog articles", articles_path %>
```
-To make this route always use the engine's `posts_path` routing helper method,
+To make this route always use the engine's `articles_path` routing helper method,
we must call the method on the routing proxy method that shares the same name as
the engine.
```erb
-<%= link_to "Blog posts", blorgh.posts_path %>
+<%= link_to "Blog articles", blorgh.articles_path %>
```
If you wish to reference the application inside the engine in a similar way, use
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 205e0f6b62..048eb9a6e3 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,23 +1,22 @@
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.
--------------------------------------------------------------------------------
NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit [the Rails API documentation](http://api.rubyonrails.org/) for a complete reference.
-
Dealing with Basic Forms
------------------------
@@ -32,18 +31,14 @@ The most basic form helper is `form_tag`.
When called without arguments like this, it creates a `<form>` tag which, when submitted, will POST to the current page. For instance, assuming the current page is `/home/index`, the generated HTML will look like this (some line breaks added for readability):
```html
-<form accept-charset="UTF-8" action="/home/index" method="post">
- <div style="margin:0;padding:0">
- <input name="utf8" type="hidden" value="&#x2713;" />
- <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
- </div>
+<form accept-charset="UTF-8" action="/" method="post">
+ <input name="utf8" type="hidden" value="&#x2713;" />
+ <input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />
Form contents
</form>
```
-Now, you'll notice that the HTML contains something extra: a `div` element with two hidden input elements inside. This div is important, because the form cannot be successfully submitted without it. The first input element with name `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](./security.html#cross-site-request-forgery-csrf).
-
-NOTE: Throughout this guide, the `div` with the hidden input elements will be excluded from code samples for brevity.
+You'll notice that the HTML contains `input` element with type `hidden`. This `input` is important, because the form cannot be successfully submitted without it. The hidden input element has name attribute of `utf8` enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name `authenticity_token` is a security feature of Rails called **cross-site request forgery protection**, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the [Security Guide](security.html#cross-site-request-forgery-csrf).
### A Generic Search Form
@@ -67,14 +62,15 @@ To create this form you will use `form_tag`, `label_tag`, `text_field_tag`, and
This will generate the following HTML:
```html
-<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
+<form accept-charset="UTF-8" action="/search" method="get">
+ <input name="utf8" type="hidden" value="&#x2713;" />
<label for="q">Search for:</label>
<input id="q" name="q" type="text" />
<input name="commit" type="submit" value="Search" />
</form>
```
-TIP: For every form input, an ID attribute is generated from its name ("q" in the example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
+TIP: For every form input, an ID attribute is generated from its name (`"q"` in above example). These IDs can be very useful for CSS styling or manipulation of form controls with JavaScript.
Besides `text_field_tag` and `submit_tag`, there is a similar helper for _every_ form control in HTML.
@@ -146,7 +142,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,
@@ -217,7 +213,7 @@ Dealing with Model Objects
### Model Object Helpers
-A particularly common task for a form is editing or creating a model object. While the `*_tag` helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the _tag suffix, for example `text_field`, `text_area`.
+A particularly common task for a form is editing or creating a model object. While the `*_tag` helpers can certainly be used for this task they are somewhat verbose as for each tag you would have to ensure the correct parameter name is used and set the default value of the input appropriately. Rails provides helpers tailored to this task. These helpers lack the `_tag` suffix, for example `text_field`, `text_area`.
For these helpers the first argument is the name of an instance variable and the second is the name of a method (usually an attribute) to call on that object. Rails will set the value of the input control to the return value of that method for the object and set an appropriate input name. If your controller has defined `@person` and that person's name is Henry then a form containing:
@@ -239,7 +235,7 @@ Rails provides helpers for displaying the validation errors associated with a mo
### Binding a Form to an Object
-While this is an increase in comfort it is far from perfect. If Person has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what `form_for` does.
+While this is an increase in comfort it is far from perfect. If `Person` has many attributes to edit then we would be repeating the name of the edited object many times. What we want to do is somehow bind a form to a model object, which is exactly what `form_for` does.
Assume we have a controller for dealing with articles `app/controllers/articles_controller.rb`:
@@ -264,7 +260,7 @@ There are a few things to note here:
* `@article` is the actual object being edited.
* There is a single hash of options. Routing options are passed in the `:url` hash, HTML options are passed in the `:html` hash. Also you can provide a `:namespace` option for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id.
* The `form_for` method yields a **form builder** object (the `f` variable).
-* Methods to create form controls are called **on** the form builder object `f`
+* Methods to create form controls are called **on** the form builder object `f`.
The resulting HTML is:
@@ -280,7 +276,7 @@ The name passed to `form_for` controls the key used in `params` to access the fo
The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder.
-You can create a similar binding without actually creating `<form>` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so:
+You can create a similar binding without actually creating `<form>` tags with the `fields_for` helper. This is useful for editing additional model objects with the same form. For example if you had a `Person` model with an associated `ContactDetail` model you could create a form for creating both like so:
```erb
<%= form_for @person, url: {action: "create"} do |person_form| %>
@@ -350,7 +346,6 @@ form_for [:admin, :management, @article]
For more information on Rails' routing system and the associated conventions, please see the [routing guide](routing.html).
-
### How do forms with PATCH, PUT, or DELETE methods work?
The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
@@ -365,12 +360,11 @@ output:
```html
<form accept-charset="UTF-8" action="/search" method="post">
- <div style="margin:0;padding:0">
- <input name="_method" type="hidden" value="patch" />
- <input name="utf8" type="hidden" value="&#x2713;" />
- <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
- </div>
+ <input name="_method" type="hidden" value="patch" />
+ <input name="utf8" type="hidden" value="&#x2713;" />
+ <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
...
+</form>
```
When parsing POSTed data, Rails will take into account the special `_method` parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example).
@@ -435,14 +429,19 @@ output:
Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option.
-TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` - you must pass 2. Be aware of values extracted from the `params` hash as they are all strings.
+TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer `2` you cannot pass `"2"` to `options_for_select` - you must pass `2`. Be aware of values extracted from the `params` hash as they are all strings.
WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true.
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:
@@ -484,11 +483,11 @@ You can also pass a block to `select` helper:
<% end %>
```
-WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly.
+WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of `ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly.
### Option Tags from a Collection of Arbitrary Objects
-Generating options tags with `options_for_select` requires that you create an array containing the text and value for each option. But what if you had a City model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them:
+Generating options tags with `options_for_select` requires that you create an array containing the text and value for each option. But what if you had a `City` model (perhaps an Active Record one) and you wanted to generate option tags from a collection of those objects? One solution would be to make a nested array by iterating over them:
```erb
<% cities_array = City.all.map { |city| [city.name, city.id] } %>
@@ -535,7 +534,7 @@ Both of these families of helpers will create a series of select boxes for the d
### Barebones Helpers
-The `select_*` family of helpers take as their first argument an instance of Date, Time or DateTime that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example
+The `select_*` family of helpers take as their first argument an instance of `Date`, `Time` or `DateTime` that is used as the currently selected value. You may omit this parameter, in which case the current date is used. For example
```erb
<%= select_date Date.today, prefix: :start_date %>
@@ -549,7 +548,7 @@ outputs (with actual option values omitted for brevity)
<select id="start_date_day" name="start_date[day]"> ... </select>
```
-The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual Time or Date object you would have to extract these values and pass them to the appropriate constructor, for example
+The above inputs would result in `params[:start_date]` being a hash with keys `:year`, `:month`, `:day`. To get an actual `Date`, `Time` or `DateTime` object you would have to extract these values and pass them to the appropriate constructor, for example
```ruby
Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i)
@@ -594,7 +593,7 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the
Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component `select_year`, `select_month`, `select_day`, `select_hour`, `select_minute`, `select_second`. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for `select_year`, "month" for `select_month` etc.) although this can be overridden with the `:field_name` option. The `:prefix` option works in the same way that it does for `select_date` and `select_time` and has the same default value.
-The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example
+The first parameter specifies which value should be selected and can either be an instance of a `Date`, `Time` or `DateTime`, in which case the relevant component will be extracted, or a numerical value. For example
```erb
<%= select_year(2009) %>
@@ -624,7 +623,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the
### What Gets Uploaded
-The object in the `params` hash is an instance of a subclass of IO. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of File backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example).
+The object in the `params` hash is an instance of a subclass of `IO`. Depending on the size of the uploaded file it may in fact be a StringIO or an instance of `File` backed by a temporary file. In both cases the object will have an `original_filename` attribute containing the name the file had on the user's computer and a `content_type` attribute containing the MIME type of the uploaded file. The following snippet saves the uploaded content in `#{Rails.root}/public/uploads` under the same name as the original file (assuming the form was the one in the previous example).
```ruby
def upload
@@ -635,7 +634,7 @@ def upload
end
```
-Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are [CarrierWave](https://github.com/jnicklas/carrierwave) and [Paperclip](http://www.thoughtbot.com/projects/paperclip).
+Once a file has been uploaded, there are a multitude of potential tasks, ranging from where to store the files (on disk, Amazon S3, etc) and associating them with models to resizing image files and generating thumbnails. The intricacies of this are beyond the scope of this guide, but there are several libraries designed to assist with these. Two of the better known ones are [CarrierWave](https://github.com/jnicklas/carrierwave) and [Paperclip](https://github.com/thoughtbot/paperclip).
NOTE: If the user has not selected a file the corresponding parameter will be an empty string.
@@ -646,7 +645,7 @@ Unlike other forms making an asynchronous file upload form is not as simple as p
Customizing Form Builders
-------------------------
-As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass FormBuilder and add the helpers there. For example
+As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of `FormBuilder` (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass `FormBuilder` and add the helpers there. For example
```erb
<%= form_for @person do |f| %>
@@ -662,7 +661,7 @@ can be replaced with
<% end %>
```
-by defining a LabellingFormBuilder class similar to the following:
+by defining a `LabellingFormBuilder` class similar to the following:
```ruby
class LabellingFormBuilder < ActionView::Helpers::FormBuilder
@@ -680,7 +679,7 @@ The form builder used also determines what happens when you do
<%= render partial: f %>
```
-If `f` is an instance of FormBuilder then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class LabellingFormBuilder then the `labelling_form` partial would be rendered instead.
+If `f` is an instance of `FormBuilder` then this will render the `form` partial, setting the partial's object to the form builder. If the form builder is of class `LabellingFormBuilder` then the `labelling_form` partial would be rendered instead.
Understanding Parameter Naming Conventions
------------------------------------------
@@ -819,21 +818,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 %>
```
@@ -857,7 +856,7 @@ Or if you don't want to render an `authenticity_token` field:
Building Complex Forms
----------------------
-Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary.
+Many apps grow beyond simple forms editing a single object. For example when creating a `Person` you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary.
### Configuring the Model
@@ -1008,4 +1007,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..25c67de993 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,10 +279,10 @@ end
and see it in action when invoking the generator:
```bash
-$ rails generate scaffold Post body:text
+$ bin/rails generate scaffold Article body:text
[...]
invoke my_helper
- create app/helpers/posts_helper.rb
+ create app/helpers/articles_helper.rb
```
We can notice on the output that our new helper was invoked instead of the Rails default. However one thing is missing, which is tests for our new generator and to do that, we are going to reuse old helpers test generators.
@@ -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 36bbd1187c..ea6c8cdd55 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -70,13 +70,11 @@ Creating a New Rails Project
The best way to use this guide is to follow each step as it happens, no code or
step needed to make this example application has been left out, so you can
-literally follow along step by step. You can get the complete code
-[here](https://github.com/rails/docrails/tree/master/guides/code/getting_started).
+literally follow along step by step.
By following along with this guide, you'll create a Rails project called
-`blog`, a
-(very) simple weblog. Before you can start building the application, you need to
-make sure that you have Rails itself installed.
+`blog`, a (very) simple weblog. Before you can start building the application,
+you need to make sure that you have Rails itself installed.
TIP: The examples below use `$` to represent your terminal prompt in a UNIX-like OS,
though it may have been customized to appear differently. If you are using Windows,
@@ -89,9 +87,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
@@ -122,10 +120,10 @@ 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.
+If it says something like "Rails 4.1.1", you are ready to continue.
### Creating the Blog Application
@@ -163,11 +161,11 @@ of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
-|bin/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
+|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, deploy or run your application.|
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
|config.ru|Rack configuration for Rack based servers used to start the application.|
|db/|Contains your current database schema, as well as the database migrations.|
-|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://gembundler.com).|
+|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see [the Bundler website](http://bundler.io).|
|lib/|Extended modules for your application.|
|log/|Application log files.|
|public/|The only folder seen by the world as-is. Contains static files and compiled assets.|
@@ -190,7 +188,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 +241,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 +265,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
@@ -358,7 +357,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 +395,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 +426,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 +554,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 +567,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 +587,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
@@ -619,6 +621,8 @@ method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
allows you to access the keys of the hash using either strings or symbols. In
this situation, the only parameters that matter are the ones from the form.
+TIP: Ensure you have a firm grasp of the `params` method, as you'll use it fairly regularly. Let's consider an example URL: **http://www.example.com/?username=dhh&email=dhh@email.com**. In this URL, `params[:username]` would equal "dhh" and `params[:email]` would equal "dhh@email.com".
+
If you re-submit the form one more time you'll now no longer get the missing
template error. Instead, you'll see something that looks like the following:
@@ -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,49 @@ 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: You might be wondering why the `A` in `Article.new` is capitalized above, whereas most other references to articles in this guide have used lowercase. In this context, we are referring to the class named `Article` that is defined in `\models\article.rb`. Class names in Ruby must begin with a capital letter.
-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 +793,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 +815,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 +871,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 +941,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 +952,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 +970,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 +1056,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 +1084,7 @@ something went wrong. To do that, you'll modify
<p>
<%= f.submit %>
</p>
+
<% end %>
<%= link_to 'Back', articles_path %>
@@ -1084,12 +1117,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 +1148,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 +1176,7 @@ it look as follows:
<p>
<%= f.submit %>
</p>
+
<% end %>
<%= link_to 'Back', articles_path %>
@@ -1138,14 +1191,26 @@ via the `PATCH` HTTP method which is the HTTP method you're expected to use to
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 +1252,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 +1270,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 +1280,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 +1293,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 +1321,7 @@ content:
<p>
<%= f.submit %>
</p>
+
<% end %>
```
@@ -1260,8 +1330,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 +1372,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 +1387,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 +1459,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 +1487,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 +1504,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 +1552,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 +1628,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 +1678,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 +1949,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 +1967,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 +2005,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 c1b575c7b7..8340d6807f 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -712,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.
@@ -847,6 +860,24 @@ en:
subject: "Welcome to Rails Guides!"
```
+To send parameters to interpolation use the `default_i18n_subject` method on the mailer.
+
+```ruby
+# user_mailer.rb
+class UserMailer < ActionMailer::Base
+ def welcome(user)
+ mail(to: user.email, subject: default_i18n_subject(user: user.name))
+ end
+end
+```
+
+```yaml
+en:
+ user_mailer:
+ welcome:
+ subject: "%{user}, welcome to Rails Guides!"
+```
+
### Overview of Other Built-In Methods that Provide I18n Support
Rails uses fixed strings and other localizations, such as format strings and other format information in a couple of helpers. Here's a brief overview.
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/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/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index bd33c5a146..5b75540c05 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -506,33 +506,33 @@ Layout declarations cascade downward in the hierarchy, and more specific layout
end
```
-* `posts_controller.rb`
+* `articles_controller.rb`
```ruby
- class PostsController < ApplicationController
+ class ArticlesController < ApplicationController
end
```
-* `special_posts_controller.rb`
+* `special_articles_controller.rb`
```ruby
- class SpecialPostsController < PostsController
+ class SpecialArticlesController < ArticlesController
layout "special"
end
```
-* `old_posts_controller.rb`
+* `old_articles_controller.rb`
```ruby
- class OldPostsController < SpecialPostsController
+ class OldArticlesController < SpecialArticlesController
layout false
def show
- @post = Post.find(params[:id])
+ @article = Article.find(params[:id])
end
def index
- @old_posts = Post.older
+ @old_articles = Article.older
render layout: "old"
end
# ...
@@ -542,10 +542,10 @@ Layout declarations cascade downward in the hierarchy, and more specific layout
In this application:
* In general, views will be rendered in the `main` layout
-* `PostsController#index` will use the `main` layout
-* `SpecialPostsController#index` will use the `special` layout
-* `OldPostsController#show` will use no layout at all
-* `OldPostsController#index` will use the `old` layout
+* `ArticlesController#index` will use the `main` layout
+* `SpecialArticlesController#index` will use the `special` layout
+* `OldArticlesController#show` will use no layout at all
+* `OldArticlesController#index` will use the `old` layout
#### Avoiding Double Render Errors
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
index 8f119f36aa..6f8584b3b7 100644
--- a/guides/source/maintenance_policy.md
+++ b/guides/source/maintenance_policy.md
@@ -3,10 +3,29 @@ Maintenance Policy for Ruby on Rails
Support of the Rails framework is divided into four groups: New features, bug
fixes, security issues, and severe security issues. They are handled as
-follows, all versions in x.y.z format
+follows, all versions in `X.Y.Z` format.
--------------------------------------------------------------------------------
+Rails follows a shifted version of [semver](http://semver.org/):
+
+**Patch `Z`**
+
+Only bug fixes, no API changes, no new features.
+Except as necessary for security fixes.
+
+**Minor `Y`**
+
+New features, may contain API changes (Serve as major versions of Semver).
+Breaking changes are paired with deprecation notices in the previous minor
+or major release.
+
+**Major `X`**
+
+New features, will likely contain API changes. The difference between Rails'
+minor and major releases is the magnitude of breaking changes, and usually
+reserved for special occasions.
+
New Features
------------
@@ -20,7 +39,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.1.z, 4.0.z
+**Currently included series:** `4.1.Z`, `4.0.Z`.
Security Issues
---------------
@@ -35,7 +54,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.1.z, 4.0.z
+**Currently included series:** `4.1.Z`, `4.0.Z`.
Severe Security Issues
----------------------
@@ -44,7 +63,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.1.z, 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/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 b1b4c8fa4e..01941fa338 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -18,7 +18,7 @@ Introduction to Rack
Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
-- [Rack API Documentation](http://rack.rubyforge.org/doc/)
+* [Rack API Documentation](http://rack.github.io/)
Explaining Rack is not really in the scope of this guide. In case you are not familiar with Rack's basics, you should check out the [Resources](#resources) section below.
@@ -111,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:
@@ -194,7 +194,7 @@ 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>
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 0783bce442..c8f8ba3044 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -183,61 +183,61 @@ You may wish to organize groups of controllers under a namespace. Most commonly,
```ruby
namespace :admin do
- resources :posts, :comments
+ resources :articles, :comments
end
```
-This will create a number of routes for each of the `posts` and `comments` controller. For `Admin::PostsController`, Rails will create:
+This will create a number of routes for each of the `articles` and `comments` controller. For `Admin::ArticlesController`, Rails will create:
-| HTTP Verb | Path | Controller#Action | Named Helper |
-| --------- | --------------------- | ------------------- | ------------------------- |
-| GET | /admin/posts | admin/posts#index | admin_posts_path |
-| GET | /admin/posts/new | admin/posts#new | new_admin_post_path |
-| POST | /admin/posts | admin/posts#create | admin_posts_path |
-| GET | /admin/posts/:id | admin/posts#show | admin_post_path(:id) |
-| GET | /admin/posts/:id/edit | admin/posts#edit | edit_admin_post_path(:id) |
-| PATCH/PUT | /admin/posts/:id | admin/posts#update | admin_post_path(:id) |
-| DELETE | /admin/posts/:id | admin/posts#destroy | admin_post_path(:id) |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | ------------------------ | ---------------------- | ---------------------------- |
+| GET | /admin/articles | admin/articles#index | admin_articles_path |
+| GET | /admin/articles/new | admin/articles#new | new_admin_article_path |
+| POST | /admin/articles | admin/articles#create | admin_articles_path |
+| GET | /admin/articles/:id | admin/articles#show | admin_article_path(:id) |
+| GET | /admin/articles/:id/edit | admin/articles#edit | edit_admin_article_path(:id) |
+| PATCH/PUT | /admin/articles/:id | admin/articles#update | admin_article_path(:id) |
+| DELETE | /admin/articles/:id | admin/articles#destroy | admin_article_path(:id) |
-If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use:
+If you want to route `/articles` (without the prefix `/admin`) to `Admin::ArticlesController`, you could use:
```ruby
scope module: 'admin' do
- resources :posts, :comments
+ resources :articles, :comments
end
```
or, for a single case:
```ruby
-resources :posts, module: 'admin'
+resources :articles, module: 'admin'
```
-If you want to route `/admin/posts` to `PostsController` (without the `Admin::` module prefix), you could use:
+If you want to route `/admin/articles` to `ArticlesController` (without the `Admin::` module prefix), you could use:
```ruby
scope '/admin' do
- resources :posts, :comments
+ resources :articles, :comments
end
```
or, for a single case:
```ruby
-resources :posts, path: '/admin/posts'
+resources :articles, path: '/admin/articles'
```
In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`:
-| HTTP Verb | Path | Controller#Action | Named Helper |
-| --------- | --------------------- | ----------------- | ------------------- |
-| GET | /admin/posts | posts#index | posts_path |
-| GET | /admin/posts/new | posts#new | new_post_path |
-| POST | /admin/posts | posts#create | posts_path |
-| GET | /admin/posts/:id | posts#show | post_path(:id) |
-| GET | /admin/posts/:id/edit | posts#edit | edit_post_path(:id) |
-| PATCH/PUT | /admin/posts/:id | posts#update | post_path(:id) |
-| DELETE | /admin/posts/:id | posts#destroy | post_path(:id) |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | ------------------------ | -------------------- | ---------------------- |
+| GET | /admin/articles | articles#index | articles_path |
+| GET | /admin/articles/new | articles#new | new_article_path |
+| POST | /admin/articles | articles#create | articles_path |
+| GET | /admin/articles/:id | articles#show | article_path(:id) |
+| GET | /admin/articles/:id/edit | articles#edit | edit_article_path(:id) |
+| PATCH/PUT | /admin/articles/:id | articles#update | article_path(:id) |
+| DELETE | /admin/articles/:id | articles#destroy | article_path(:id) |
TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo' => '/foo#index'`._
@@ -304,7 +304,7 @@ TIP: _Resources should never be nested more than 1 level deep._
One way to avoid deep nesting (as recommended above) is to generate the collection actions scoped under the parent, so as to get a sense of the hierarchy, but to not nest the member actions. In other words, to only build routes with the minimal amount of information to uniquely identify the resource, like this:
```ruby
-resources :posts do
+resources :articles do
resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]
@@ -313,7 +313,7 @@ resources :comments, only: [:show, :edit, :update, :destroy]
This idea strikes a balance between descriptive routes and deep nesting. There exists shorthand syntax to achieve just that, via the `:shallow` option:
```ruby
-resources :posts do
+resources :articles do
resources :comments, shallow: true
end
```
@@ -321,7 +321,7 @@ end
This will generate the exact same routes as the first example. You can also specify the `:shallow` option in the parent resource, in which case all of the nested resources will be shallow:
```ruby
-resources :posts, shallow: true do
+resources :articles, shallow: true do
resources :comments
resources :quotes
resources :drafts
@@ -332,7 +332,7 @@ The `shallow` method of the DSL creates a scope inside of which every nesting is
```ruby
shallow do
- resources :posts do
+ resources :articles do
resources :comments
resources :quotes
resources :drafts
@@ -344,7 +344,7 @@ There exist two options for `scope` to customize shallow routes. `:shallow_path`
```ruby
scope shallow_path: "sekret" do
- resources :posts do
+ resources :articles do
resources :comments, shallow: true
end
end
@@ -352,21 +352,21 @@ end
The comments resource here will have the following routes generated for it:
-| HTTP Verb | Path | Controller#Action | Named Helper |
-| --------- | -------------------------------------- | ----------------- | --------------------- |
-| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments_path |
-| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments_path |
-| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment_path |
-| GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment_path |
-| GET | /sekret/comments/:id(.:format) | comments#show | comment_path |
-| PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment_path |
-| DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment_path |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | -------------------------------------------- | ----------------- | ------------------------ |
+| GET | /articles/:article_id/comments(.:format) | comments#index | article_comments_path |
+| POST | /articles/:article_id/comments(.:format) | comments#create | article_comments_path |
+| GET | /articles/:article_id/comments/new(.:format) | comments#new | new_article_comment_path |
+| GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment_path |
+| GET | /sekret/comments/:id(.:format) | comments#show | comment_path |
+| PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment_path |
+| DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment_path |
The `:shallow_prefix` option adds the specified parameter to the named helpers:
```ruby
scope shallow_prefix: "sekret" do
- resources :posts do
+ resources :articles do
resources :comments, shallow: true
end
end
@@ -374,15 +374,15 @@ end
The comments resource here will have the following routes generated for it:
-| HTTP Verb | Path | Controller#Action | Named Helper |
-| --------- | -------------------------------------- | ----------------- | ------------------------ |
-| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments_path |
-| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments_path |
-| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment_path |
-| GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment_path |
-| GET | /comments/:id(.:format) | comments#show | sekret_comment_path |
-| PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment_path |
-| DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment_path |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | -------------------------------------------- | ----------------- | --------------------------- |
+| GET | /articles/:article_id/comments(.:format) | comments#index | article_comments_path |
+| POST | /articles/:article_id/comments(.:format) | comments#create | article_comments_path |
+| GET | /articles/:article_id/comments/new(.:format) | comments#new | new_article_comment_path |
+| GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment_path |
+| GET | /comments/:id(.:format) | comments#show | sekret_comment_path |
+| PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment_path |
+| DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment_path |
### Routing concerns
@@ -403,7 +403,7 @@ These concerns can be used in resources to avoid code duplication and share beha
```ruby
resources :messages, concerns: :commentable
-resources :posts, concerns: [:commentable, :image_attachable]
+resources :articles, concerns: [:commentable, :image_attachable]
```
The above is equivalent to:
@@ -413,7 +413,7 @@ resources :messages do
resources :comments
end
-resources :posts do
+resources :articles do
resources :comments
resources :images, only: :index
end
@@ -422,7 +422,7 @@ end
Also you can use them in any place that you want inside the routes, for example in a scope or namespace call:
```ruby
-namespace :posts do
+namespace :articles do
concerns :commentable
end
```
@@ -662,26 +662,26 @@ 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: 'articles#show', constraints: { id: /^\d/ }
```
However, note that you don't need to use anchors because all routes are anchored at the start.
-For example, the following routes would allow for `posts` with `to_param` values like `1-hello-world` that always begin with a number and `users` with `to_param` values like `david` that never begin with a number to share the root namespace:
+For example, the following routes would allow for `articles` with `to_param` values like `1-hello-world` that always begin with a number and `users` with `to_param` values like `david` that never begin with a number to share the root namespace:
```ruby
-get '/:id', to: 'posts#show', constraints: { id: /\d.+/ }
+get '/:id', to: 'articles#show', constraints: { id: /\d.+/ }
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,7 +694,7 @@ namespace :admin do
end
```
-NOTE: Request constraints work by calling a method on the <a href="action_controller_overview.html#the-request-object">Request object</a> 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.
+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
@@ -771,20 +771,20 @@ get '*pages', to: 'pages#show', format: true
You can redirect any path to another path using the `redirect` helper in your router:
```ruby
-get '/stories', to: redirect('/posts')
+get '/stories', to: redirect('/articles')
```
You can also reuse dynamic segments from the match in the path to redirect to:
```ruby
-get '/stories/:name', to: redirect('/posts/%{name}')
+get '/stories/:name', to: redirect('/articles/%{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| "/articles/#{path_params[:name].pluralize}" }
+get '/stories', to: redirect { |path_params, req| "/articles/#{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.
@@ -793,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 `'articles#index'`, which corresponds to the `index` action in the `ArticlesController`, you can specify any [Rack application](rails_on_rack.html) as the endpoint for a matcher:
```ruby
match '/application.js', to: Sprockets, via: :all
@@ -801,7 +801,7 @@ match '/application.js', to: Sprockets, via: :all
As long as `Sprockets` responds to `call` and returns a `[status, headers, body]`, the router won't know the difference between the Rack application and an action. This is an appropriate use of `via: :all`, as you will want to allow your Rack application to handle all verbs as it considers appropriate.
-NOTE: For the curious, `'posts#index'` actually expands out to `PostsController.action(:index)`, which returns a valid Rack application.
+NOTE: For the curious, `'articles#index'` actually expands out to `ArticlesController.action(:index)`, which returns a valid Rack application.
### Using `root`
@@ -837,7 +837,7 @@ get 'こんにちは', to: 'welcome#index'
Customizing Resourceful Routes
------------------------------
-While the default routes and helpers generated by `resources :posts` will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.
+While the default routes and helpers generated by `resources :articles` will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.
### Specifying a Controller to Use
@@ -879,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.
@@ -919,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' }
@@ -954,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`:
@@ -974,15 +974,15 @@ You can prefix routes with a named parameter also:
```ruby
scope ':username' do
- resources :posts
+ resources :articles
end
```
-This will provide you with URLs such as `/bob/posts/1` and will allow you to reference the `username` part of the path as `params[:username]` in controllers, helpers and views.
+This will provide you with URLs such as `/bob/articles/1` and will allow you to reference the `username` part of the path as `params[:username]` in controllers, helpers and views.
### 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]
@@ -1044,6 +1044,28 @@ end
This will create routing helpers such as `magazine_periodical_ads_url` and `edit_magazine_periodical_ad_path`.
+### Overriding Named Route Parameters
+
+The `:param` option overrides the default resource identifier `:id` (name of
+the [dynamic segment](routing.html#dynamic-segments) used to generate the
+routes). You can access that segment from your controller using
+`params[<:param>]`.
+
+```ruby
+resources :videos, param: :identifier
+```
+
+```
+ videos GET /videos(.:format) videos#index
+ POST /videos(.:format) videos#create
+ new_videos GET /videos/new(.:format) videos#new
+edit_videos GET /videos/:identifier/edit(.:format) videos#edit
+```
+
+```ruby
+Video.find_by(identifier: params[:identifier])
+```
+
Inspecting and Testing Routes
-----------------------------
@@ -1072,7 +1094,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/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index 8faf03e58c..f0230b428b 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -13,7 +13,7 @@ After reading this guide, you will know:
Markdown
-------
-Guides are written in [GitHub Flavored Markdown](http://github.github.com/github-flavored-markdown/). There is comprehensive [documentation for Markdown](http://daringfireball.net/projects/markdown/syntax), a [cheatsheet](http://daringfireball.net/projects/markdown/basics), and [additional documentation](http://github.github.com/github-flavored-markdown/) on the differences from traditional Markdown.
+Guides are written in [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown). There is comprehensive [documentation for Markdown](http://daringfireball.net/projects/markdown/syntax), a [cheatsheet](http://daringfireball.net/projects/markdown/basics).
Prologue
--------
diff --git a/guides/source/security.md b/guides/source/security.md
index 9d7fdb3c6d..7e39986f8b 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.
@@ -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
@@ -374,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.
@@ -406,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
@@ -732,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)
@@ -741,7 +741,7 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title))
This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags.
-As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &amp;, &quot;, &lt;, &gt; by their uninterpreted representations in HTML (`&amp;`, `&quot;`, `&lt`;, and `&gt;`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the [SafeErb](http://safe-erb.rubyforge.org/svn/plugins/safe_erb/) plugin_. SafeErb reminds you to escape strings from external sources.
+As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &amp;, &quot;, &lt;, &gt; by their uninterpreted representations in HTML (`&amp;`, `&quot;`, `&lt`;, and `&gt;`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the SafeErb gem. SafeErb reminds you to escape strings from external sources.
##### Obfuscation and Encoding Injection
@@ -812,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 36d37f3af0..c01b2e575a 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -49,7 +49,9 @@ The `test_helper.rb` file holds the default configuration for your tests.
### The Low-Down on Fixtures
-For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures.
+For good tests, you'll need to give some thought to setting up test data.
+In Rails, you can handle this by defining and customizing fixtures.
+You can find comprehensive documentation in the [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
#### What Are Fixtures?
@@ -94,6 +96,12 @@ one:
category: about
```
+Note: For associations to reference one another by name, you cannot specify the `id:`
+ attribute on the fixtures. Rails will auto assign a primary key to be consistent between
+ runs. If you manually specify an `id:` attribute, this behavior will not work. For more
+ information on this assocation behavior please read the
+ [fixture api documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html).
+
#### ERB'in It Up
ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users:
@@ -134,27 +142,27 @@ 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 article title:string body:text
...
-create app/models/post.rb
-create test/models/post_test.rb
-create test/fixtures/posts.yml
+create app/models/article.rb
+create test/models/article_test.rb
+create test/fixtures/articles.yml
...
```
-The default test stub in `test/models/post_test.rb` looks like this:
+The default test stub in `test/models/article_test.rb` looks like this:
```ruby
require 'test_helper'
-class PostTest < ActiveSupport::TestCase
+class ArticleTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
@@ -170,15 +178,15 @@ require 'test_helper'
As you know by now, `test_helper.rb` specifies the default configuration to run our tests. This is included with all the tests, so any methods added to this file are available to all your tests.
```ruby
-class PostTest < ActiveSupport::TestCase
+class ArticleTest < ActiveSupport::TestCase
```
-The `PostTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `PostTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide.
+The `ArticleTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `ArticleTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide.
-Any method defined within a class inherited from `MiniTest::Unit::TestCase`
-(which is the superclass of `ActiveSupport::TestCase`) that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run.
+Any method defined within a class inherited from `Minitest::Test`
+(which is the superclass of `ActiveSupport::TestCase`) that begins with `test_` (case sensitive) is simply called a test. So, `test_password` and `test_valid_password` are legal test names and are run automatically when the test case is run.
-Rails adds a `test` method that takes a test name and a block. It generates a normal `MiniTest::Unit` test with method names prefixed with `test_`. So,
+Rails adds a `test` method that takes a test name and a block. It generates a normal `Minitest::Unit` test with method names prefixed with `test_`. So,
```ruby
test "the truth" do
@@ -220,7 +228,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/article_test.rb
.
Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
@@ -231,7 +239,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/article_test.rb test_the_truth
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
@@ -243,25 +251,25 @@ This will run all test methods from the test case. Note that `test_helper.rb` is
The `.` (dot) above indicates a passing test. When a test fails you see an `F`; when a test throws an error you see an `E` in its place. The last line of the output is the summary.
-To see how a test failure is reported, you can add a failing test to the `post_test.rb` test case.
+To see how a test failure is reported, you can add a failing test to the `article_test.rb` test case.
```ruby
-test "should not save post without title" do
- post = Post.new
- assert_not post.save
+test "should not save article without title" do
+ article = Article.new
+ assert_not article.save
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/article_test.rb test_should_not_save_article_without_title
F
Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.
1) Failure:
-test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
+test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]:
Failed assertion, no message given.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
@@ -270,9 +278,9 @@ Failed assertion, no message given.
In the output, `F` denotes a failure. You can see the corresponding trace shown under `1)` along with the name of the failing test. The next few lines contain the stack trace followed by a message which mentions the actual value and the expected value by the assertion. The default assertion messages provide just enough information to help pinpoint the error. To make the assertion failure message more readable, every assertion provides an optional message parameter, as shown here:
```ruby
-test "should not save post without title" do
- post = Post.new
- assert_not post.save, "Saved the post without a title"
+test "should not save article without title" do
+ article = Article.new
+ assert_not article.save, "Saved the article without a title"
end
```
@@ -280,14 +288,14 @@ Running this test shows the friendlier assertion message:
```bash
1) Failure:
-test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
-Saved the post without a title
+test_should_not_save_article_without_title(ArticleTest) [test/models/article_test.rb:6]:
+Saved the article without a title
```
Now to get this test to pass we can add a model level validation for the _title_ field.
```ruby
-class Post < ActiveRecord::Base
+class Article < ActiveRecord::Base
validates :title, presence: true
end
```
@@ -295,7 +303,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/article_test.rb test_should_not_save_article_without_title
.
Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
@@ -320,15 +328,15 @@ 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/article_test.rb test_should_report_error
E
Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.
1) Error:
-test_should_report_error(PostTest):
-NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x007fe32e24afe0>
- test/models/post_test.rb:10:in `block in <class:PostTest>'
+test_should_report_error(ArticleTest):
+NameError: undefined local variable or method `some_undefined_variable' for #<ArticleTest:0x007fe32e24afe0>
+ test/models/article_test.rb:10:in `block in <class:ArticleTest>'
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
```
@@ -345,7 +353,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/article_test.rb
```
### What to Include in Your Unit Tests
@@ -393,7 +401,7 @@ NOTE: Creating your own assertions is an advanced topic that we won't cover in t
### Rails Specific Assertions
-Rails adds some custom assertions of its own to the `test/unit` framework:
+Rails adds some custom assertions of its own to the `minitest` framework:
| Assertion | Purpose |
| --------------------------------------------------------------------------------- | ------- |
@@ -422,26 +430,26 @@ You should test for things such as:
* was the correct object stored in the response template?
* was the appropriate message displayed to the user in the view?
-Now that we have used Rails scaffold generator for our `Post` resource, it has already created the controller code and tests. You can take look at the file `posts_controller_test.rb` in the `test/controllers` directory.
+Now that we have used Rails scaffold generator for our `Article` resource, it has already created the controller code and tests. You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory.
-Let me take you through one such test, `test_should_get_index` from the file `posts_controller_test.rb`.
+Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`.
```ruby
-class PostsControllerTest < ActionController::TestCase
+class ArticlesControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
- assert_not_nil assigns(:posts)
+ assert_not_nil assigns(:articles)
end
end
```
-In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that it assigns a valid `posts` instance variable.
+In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that it assigns a valid `articles` instance variable.
The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments:
* The action of the controller you are requesting. This can be in the form of a string or a symbol.
-* An optional hash of request parameters to pass into the action (eg. query string parameters or post variables).
+* An optional hash of request parameters to pass into the action (eg. query string parameters or article variables).
* An optional hash of session variables to pass along with the request.
* An optional hash of flash values.
@@ -457,17 +465,17 @@ Another example: Calling the `:view` action, passing an `id` of 12 as the `param
get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})
```
-NOTE: If you try running `test_should_create_post` test from `posts_controller_test.rb` it will fail on account of the newly added model level validation and rightly so.
+NOTE: If you try running `test_should_create_article` test from `articles_controller_test.rb` it will fail on account of the newly added model level validation and rightly so.
-Let us modify `test_should_create_post` test in `posts_controller_test.rb` so that all our test pass:
+Let us modify `test_should_create_article` test in `articles_controller_test.rb` so that all our test pass:
```ruby
-test "should create post" do
- assert_difference('Post.count') do
- post :create, post: {title: 'Some title'}
+test "should create article" do
+ assert_difference('Article.count') do
+ post :create, article: {title: 'Some title'}
end
- assert_redirected_to post_path(assigns(:post))
+ assert_redirected_to article_path(assigns(:article))
end
```
@@ -576,12 +584,12 @@ is the correct way to assert for the layout when the view renders a partial with
Here's another example that uses `flash`, `assert_redirected_to`, and `assert_difference`:
```ruby
-test "should create post" do
- assert_difference('Post.count') do
- post :create, post: {title: 'Hi', body: 'This is my first post.'}
+test "should create article" do
+ assert_difference('article.count') do
+ post :create, article: {title: 'Hi', body: 'This is my first article.'}
end
- assert_redirected_to post_path(assigns(:post))
- assert_equal 'Post was successfully created.', flash[:notice]
+ assert_redirected_to article_path(assigns(:article))
+ assert_equal 'Article was successfully created.', flash[:notice]
end
```
@@ -653,7 +661,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
```
@@ -699,8 +707,6 @@ A simple integration test that exercises multiple controllers:
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
- fixtures :users
-
test "login and browse site" do
# login via https
https!
@@ -712,7 +718,7 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
assert_equal 'Welcome david!', flash[:notice]
https!(false)
- get "/posts/all"
+ get "/articles/all"
assert_response :success
assert assigns(:products)
end
@@ -727,10 +733,7 @@ Here's an example of multiple sessions and custom DSL in an integration test
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
- fixtures :users
-
test "login and browse site" do
-
# User david logs in
david = login(:david)
# User guest logs in
@@ -793,57 +796,53 @@ when you initiate a Rails project.
| `rake test:all:db` | Runs all tests quickly by merging all types and resetting db |
-Brief Note About `MiniTest`
+Brief Note About `Minitest`
-----------------------------
-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.
+Ruby ships with a vast Standard Library for all common use-cases including testing. Since version 1.9, Ruby provides `Minitest`, a framework for testing. All the basic assertions such as `assert_equal` discussed above are actually defined in `Minitest::Assertions`. The classes `ActiveSupport::TestCase`, `ActionController::TestCase`, `ActionMailer::TestCase`, `ActionView::TestCase` and `ActionDispatch::IntegrationTest` - which we have been inheriting in our test classes - include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
-NOTE: For more information on `Test::Unit`, refer to [test/unit Documentation](http://ruby-doc.org/stdlib/libdoc/test/unit/rdoc/)
-For more information on `MiniTest`, refer to [Minitest](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/minitest/unit/rdoc/)
+NOTE: For more information on `Minitest`, refer to [Minitest](http://ruby-doc.org/stdlib-2.1.0/libdoc/minitest/rdoc/MiniTest.html)
Setup and Teardown
------------------
-If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Posts` controller:
+If you would like to run a block of code before the start of each test and another block of code after the end of each test you have two special callbacks for your rescue. Let's take note of this by looking at an example for our functional test in `Articles` controller:
```ruby
require 'test_helper'
-class PostsControllerTest < ActionController::TestCase
+class ArticlesControllerTest < ActionController::TestCase
# called before every single test
def setup
- @post = posts(:one)
+ @article = articles(:one)
end
# called after every single test
def teardown
- # as we are re-initializing @post before every test
+ # as we are re-initializing @article before every test
# setting it to nil here is not essential but I hope
# you understand how you can use the teardown method
- @post = nil
+ @article = nil
end
- test "should show post" do
- get :show, id: @post.id
+ test "should show article" do
+ get :show, id: @article.id
assert_response :success
end
- test "should destroy post" do
- assert_difference('Post.count', -1) do
- delete :destroy, id: @post.id
+ test "should destroy article" do
+ assert_difference('Article.count', -1) do
+ delete :destroy, id: @article.id
end
- assert_redirected_to posts_path
+ assert_redirected_to articles_path
end
end
```
-Above, the `setup` method is called before each test and so `@post` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using:
+Above, the `setup` method is called before each test and so `@article` is available for each of the tests. Rails implements `setup` and `teardown` as `ActiveSupport::Callbacks`. Which essentially means you need not only use `setup` and `teardown` as methods in your tests. You could specify them by using:
* a block
* a method (like in the earlier example)
@@ -855,38 +854,38 @@ Let's see the earlier example by specifying `setup` callback by specifying a met
```ruby
require 'test_helper'
-class PostsControllerTest < ActionController::TestCase
+class ArticlesControllerTest < ActionController::TestCase
# called before every single test
- setup :initialize_post
+ setup :initialize_article
# called after every single test
def teardown
- @post = nil
+ @article = nil
end
- test "should show post" do
- get :show, id: @post.id
+ test "should show article" do
+ get :show, id: @article.id
assert_response :success
end
- test "should update post" do
- patch :update, id: @post.id, post: {}
- assert_redirected_to post_path(assigns(:post))
+ test "should update article" do
+ patch :update, id: @article.id, article: {}
+ assert_redirected_to article_path(assigns(:article))
end
- test "should destroy post" do
- assert_difference('Post.count', -1) do
- delete :destroy, id: @post.id
+ test "should destroy article" do
+ assert_difference('Article.count', -1) do
+ delete :destroy, id: @article.id
end
- assert_redirected_to posts_path
+ assert_redirected_to articles_path
end
private
- def initialize_post
- @post = posts(:one)
+ def initialize_article
+ @article = articles(:one)
end
end
```
@@ -894,11 +893,11 @@ end
Testing Routes
--------------
-Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default `show` action of `Posts` controller above should look like:
+Like everything else in your Rails application, it is recommended that you test your routes. An example test for a route in the default `show` action of `Articles` controller above should look like:
```ruby
-test "should route to post" do
- assert_routing '/posts/1', {controller: "posts", action: "show", id: "1"}
+test "should route to article" do
+ assert_routing '/articles/1', {controller: "articles", action: "show", id: "1"}
end
```
@@ -1011,7 +1010,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
@@ -1046,7 +1045,7 @@ access to Rails' helper methods such as `link_to` or `pluralize`.
Other Testing Approaches
------------------------
-The built-in `test/unit` based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including:
+The built-in `minitest` based testing is not the only way to test Rails applications. Rails developers have come up with a wide variety of other approaches and aids for testing, including:
* [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use.
* [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index da161f84c9..6800e71a3c 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -22,6 +22,12 @@ Rails generally stays close to the latest released Ruby version when it's releas
TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump straight to 1.9.3 for smooth sailing.
+Upgrading from Rails 4.1 to Rails 4.2
+-------------------------------------
+
+NOTE: This section is a work in progress.
+
+
Upgrading from Rails 4.0 to Rails 4.1
-------------------------------------
@@ -82,10 +88,10 @@ secrets, you need to:
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`
+ 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.
4. Use `rake secret` to generate new keys for the `development` and `test` sections.
@@ -393,6 +399,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
-------------------------------------
@@ -480,7 +494,7 @@ def update
respond_to do |format|
format.json do
# perform a partial update
- @post.update params[:post]
+ @article.update params[:article]
end
format.json_patch do
@@ -712,17 +726,18 @@ config.assets.js_compressor = :uglifier
Upgrading from Rails 3.1 to Rails 3.2
-------------------------------------
-If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
+If your application is currently on any version of Rails older than 3.1.x, you
+should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
-The following changes are meant for upgrading your application to Rails 3.2.17,
-the last 3.2.x version of Rails.
+The following changes are meant for upgrading your application to the latest
+3.2.x version of Rails.
### Gemfile
Make the following changes to your `Gemfile`.
```ruby
-gem 'rails', '3.2.17'
+gem 'rails', '3.2.18'
group :assets do
gem 'sass-rails', '~> 3.2.6'
@@ -883,7 +898,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/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index aba3c9ed61..7c3fd9f69d 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -158,7 +158,7 @@ is a helper that assists with writing forms. `form_for` takes a `:remote`
option. It works like this:
```erb
-<%= form_for(@post, remote: true) do |f| %>
+<%= form_for(@article, remote: true) do |f| %>
...
<% end %>
```
@@ -166,7 +166,7 @@ option. It works like this:
This will generate the following HTML:
```html
-<form accept-charset="UTF-8" action="/posts" class="new_post" data-remote="true" id="new_post" method="post">
+<form accept-charset="UTF-8" action="/articles" class="new_article" data-remote="true" id="new_article" method="post">
...
</form>
```
@@ -180,10 +180,10 @@ bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out:
```coffeescript
$(document).ready ->
- $("#new_post").on("ajax:success", (e, data, status, xhr) ->
- $("#new_post").append xhr.responseText
+ $("#new_article").on("ajax:success", (e, data, status, xhr) ->
+ $("#new_article").append xhr.responseText
).on "ajax:error", (e, xhr, status, error) ->
- $("#new_post").append "<p>ERROR</p>"
+ $("#new_article").append "<p>ERROR</p>"
```
Obviously, you'll want to be a bit more sophisticated than that, but it's a
@@ -196,7 +196,7 @@ is very similar to `form_for`. It has a `:remote` option that you can use like
this:
```erb
-<%= form_tag('/posts', remote: true) do %>
+<%= form_tag('/articles', remote: true) do %>
...
<% end %>
```
@@ -204,7 +204,7 @@ this:
This will generate the following HTML:
```html
-<form accept-charset="UTF-8" action="/posts" data-remote="true" method="post">
+<form accept-charset="UTF-8" action="/articles" data-remote="true" method="post">
...
</form>
```
@@ -219,21 +219,21 @@ is a helper that assists with generating links. It has a `:remote` option you
can use like this:
```erb
-<%= link_to "a post", @post, remote: true %>
+<%= link_to "an article", @article, remote: true %>
```
which generates
```html
-<a href="/posts/1" data-remote="true">a post</a>
+<a href="/articles/1" data-remote="true">an article</a>
```
You can bind to the same Ajax events as `form_for`. Here's an example. Let's
-assume that we have a list of posts that can be deleted with just one
+assume that we have a list of articles that can be deleted with just one
click. We would generate some HTML like this:
```erb
-<%= link_to "Delete post", @post, remote: true, method: :delete %>
+<%= link_to "Delete article", @article, remote: true, method: :delete %>
```
and write some CoffeeScript like this:
@@ -241,7 +241,7 @@ and write some CoffeeScript like this:
```coffeescript
$ ->
$("a[data-remote]").on "ajax:success", (e, data, status, xhr) ->
- alert "The post was deleted."
+ alert "The article was deleted."
```
### button_to
@@ -249,14 +249,14 @@ $ ->
[`button_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to) is a helper that helps you create buttons. It has a `:remote` option that you can call like this:
```erb
-<%= button_to "A post", @post, remote: true %>
+<%= button_to "An article", @article, remote: true %>
```
this generates
```html
-<form action="/posts/1" class="button_to" data-remote="true" method="post">
- <div><input type="submit" value="A post"></div>
+<form action="/articles/1" class="button_to" data-remote="true" method="post">
+ <div><input type="submit" value="An article"></div>
</form>
```
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 6a31a923a7..3350b1a4b2 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,29 @@
+* Invalid `bin/rails generate` commands will now show spelling suggestions.
+
+ *Richard Schneeman*
+
+* Add `bin/setup` script to bootstrap an application.
+
+ *Yves Senn*
+
+* Replace double quotes with single quotes while adding an entry into Gemfile.
+
+ *Alexander Belaev*
+
+* 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.
@@ -8,10 +34,6 @@
*Matthew Draper*
-* Do not set the Rails environment to test by default when using test_unit Railtie.
-
- *Konstantin Shabanov*
-
* Remove sqlite3 lines from `.gitignore` if the application is not using sqlite3.
*Dmitrii Golub*
diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc
index eccdee7b07..8d847eaa1c 100644
--- a/railties/RDOC_MAIN.rdoc
+++ b/railties/RDOC_MAIN.rdoc
@@ -1,7 +1,7 @@
== Welcome to \Rails
\Rails is a web-application framework that includes everything needed to create
-database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern.
+database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model-view-controller] pattern.
Understanding the MVC pattern is key to understanding \Rails. MVC divides your application
into three layers, each with a specific responsibility.
diff --git a/railties/README.rdoc b/railties/README.rdoc
index 6248b5feed..a25658668c 100644
--- a/railties/README.rdoc
+++ b/railties/README.rdoc
@@ -31,7 +31,11 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+Bug reports can be filed for the Ruby on Rails project here:
* https://github.com/rails/rails/issues
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
+
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 2fde974732..362713eb75 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -364,6 +364,10 @@ module Rails
end
end
+ def migration_railties # :nodoc:
+ (ordered_railties & railties_without_main_app).reverse
+ end
+
protected
alias :build_middleware_stack :app
@@ -394,6 +398,11 @@ module Rails
super
end
+ def railties_without_main_app # :nodoc:
+ @railties_without_main_app ||= Rails::Railtie.subclasses.map(&:instance) +
+ Rails::Engine.subclasses.map(&:instance)
+ end
+
# Returns the ordered railties for this application considering railties_order.
def ordered_railties #:nodoc:
@ordered_railties ||= begin
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/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/generators.rb b/railties/lib/rails/generators.rb
index dce734b54e..bf2390cb7e 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -156,10 +156,20 @@ module Rails
args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
klass.start(args, config)
else
- puts "Could not find generator #{namespace}."
+ options = sorted_groups.map(&:last).flatten
+ suggestions = options.sort_by {|suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
+ msg = "Could not find generator '#{namespace}'. "
+ msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.join(" or ") }\n"
+ msg << "Run `rails generate --help` for more options."
+ puts msg
end
end
+ # Returns an array of generator namespaces that are hidden.
+ # Generator namespaces may be hidden for a variety of reasons.
+ # Some are aliased such as "rails:migration" and can be
+ # invoked with the shorter "migration", others are private to other generators
+ # such as "css:scaffold".
def self.hidden_namespaces
@hidden_namespaces ||= begin
orm = options[:rails][:orm]
@@ -199,17 +209,6 @@ module Rails
# Show help message with available generators.
def self.help(command = 'generate')
- lookup!
-
- namespaces = subclasses.map{ |k| k.namespace }
- namespaces.sort!
-
- groups = Hash.new { |h,k| h[k] = [] }
- namespaces.each do |namespace|
- base = namespace.split(':').first
- groups[base] << namespace
- end
-
puts "Usage: rails #{command} GENERATOR [args] [options]"
puts
puts "General options:"
@@ -222,20 +221,74 @@ module Rails
puts "Please choose a generator below."
puts
- # Print Rails defaults first.
+ print_generators
+ end
+
+ def self.public_namespaces
+ lookup!
+ subclasses.map { |k| k.namespace }
+ end
+
+ def self.print_generators
+ sorted_groups.each { |b, n| print_list(b, n) }
+ end
+
+ def self.sorted_groups
+ namespaces = public_namespaces
+ namespaces.sort!
+ groups = Hash.new { |h,k| h[k] = [] }
+ namespaces.each do |namespace|
+ base = namespace.split(':').first
+ groups[base] << namespace
+ end
rails = groups.delete("rails")
rails.map! { |n| n.sub(/^rails:/, '') }
rails.delete("app")
rails.delete("plugin")
- print_list("rails", rails)
hidden_namespaces.each { |n| groups.delete(n.to_s) }
- groups.sort.each { |b, n| print_list(b, n) }
+ [["rails", rails]] + groups.sort.to_a
end
protected
+ # This code is based directly on the Text gem implementation
+ # Returns a value representing the "cost" of transforming str1 into str2
+ def self.levenshtein_distance str1, str2
+ s = str1
+ t = str2
+ n = s.length
+ m = t.length
+ max = n/2
+
+ return m if (0 == n)
+ return n if (0 == m)
+ return n if (n - m).abs > max
+
+ d = (0..m).to_a
+ x = nil
+
+ str1.each_char.each_with_index do |char1,i|
+ e = i+1
+
+ str2.each_char.each_with_index do |char2,j|
+ cost = (char1 == char2) ? 0 : 1
+ x = [
+ d[j+1] + 1, # insertion
+ e + 1, # deletion
+ d[j] + cost # substitution
+ ].min
+ d[j] = e
+ e = x
+ end
+
+ d[m] = x
+ end
+
+ return x
+ end
+
# Prints a list of generators.
def self.print_list(base, namespaces) #:nodoc:
namespaces = namespaces.reject do |n|
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 625f031c94..a239874df0 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -20,9 +20,9 @@ module Rails
# Set the message to be shown in logs. Uses the git repo if one is given,
# otherwise use name (version).
- parts, message = [ name.inspect ], name
+ parts, message = [ quote(name) ], name
if version ||= options.delete(:version)
- parts << version.inspect
+ parts << quote(version)
message << " (#{version})"
end
message = options[:git] if options[:git]
@@ -30,7 +30,7 @@ module Rails
log :gemfile, message
options.each do |option, value|
- parts << "#{option}: #{value.inspect}"
+ parts << "#{option}: #{quote(value)}"
end
in_root do
@@ -68,7 +68,7 @@ module Rails
log :source, source
in_root do
- prepend_file "Gemfile", "source #{source.inspect}\n", verbose: false
+ prepend_file "Gemfile", "source #{quote(source)}\n", verbose: false
end
end
@@ -255,6 +255,15 @@ module Rails
end
end
+ # Surround string with single quotes if there is no quotes.
+ # Otherwise fall back to double quotes
+ def quote(str)
+ if str.include?("'")
+ str.inspect
+ else
+ "'#{str}'"
+ end
+ end
end
end
end
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 10f80abb15..da99e74435 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -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/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 5a92ab3e95..b7da44ca2d 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -30,7 +30,12 @@ module Rails
protected
attr_reader :file_name
- alias :singular_name :file_name
+
+ # FIXME: We are avoiding to use alias because a bug on thor that make
+ # this method public and add it to the task list.
+ def singular_name
+ file_name
+ end
# Wrap block with namespace of current application
# if namespace exists and is not skipped
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 8675d8bc1e..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
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 448b6f4845..5bdbd58097 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -16,7 +16,7 @@ source 'https://rubygems.org'
# 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/railties/lib/rails/generators/rails/app/templates/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup
new file mode 100644
index 0000000000..0e22b3fa5c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup
@@ -0,0 +1,28 @@
+require 'pathname'
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+Dir.chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file:
+
+ puts "== Installing dependencies =="
+ system "gem install bundler --conservative"
+ system "bundle check || bundle install"
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?("config/database.yml")
+ # system "cp config/database.yml.sample config/database.yml"
+ # end
+
+ puts "\n== Preparing database =="
+ system "bin/rake db:setup"
+
+ puts "\n== Removing old logs and tempfiles =="
+ system "rm -f log/*"
+ system "rm -rf tmp/cache"
+
+ puts "\n== Restarting application server =="
+ system "touch tmp/restart.txt"
+end
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 d088dd62bf..187ff01bac 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
@@ -1,7 +1,7 @@
# IBM Dataservers
#
# Home Page
-# http://rubyforge.org/projects/rubyibm/
+# https://github.com/dparnell/ibm_db
#
# To install the ibm_db gem:
#
@@ -31,8 +31,6 @@
# Configure Using Gemfile
# gem 'ibm_db'
#
-# For more details on the installation and the connection parameters below,
-# please refer to the latest documents at http://rubyforge.org/docman/?group_id=2361
#
default: &default
adapter: ibm_db
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 10ab4c02e2..9aedcc15cb 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
@@ -1,7 +1,7 @@
# Oracle/OCI 8i, 9, 10g
#
# Requires Ruby/OCI8:
-# http://rubyforge.org/projects/ruby-oci8/
+# https://github.com/kubo/ruby-oci8
#
# Specify your database using any valid connection syntax, such as a
# tnsnames.ora service name, or an SQL connect string of the form:
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 9ed71687ea..a2aa7c09db 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
@@ -16,10 +16,10 @@ Rails.application.configure do
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
- # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
+ # For large-scale production use, consider using a caching reverse proxy like NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
- # Disable Rails's static asset server (Apache or nginx will already do this).
+ # Disable Rails's static asset server (Apache or NGINX will already do this).
config.serve_static_assets = false
<%- unless options.skip_sprockets? -%>
@@ -37,8 +37,8 @@ Rails.application.configure do
<%- end -%>
# Specifies the header that your server uses for sending files.
- # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
- # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
+ # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index 6b011e577a..87b8fe3516 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -5,9 +5,6 @@ require 'rails/test_help'
class ActiveSupport::TestCase
<% unless options[:skip_active_record] -%>
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
- #
- # Note: You'll currently still have to declare fixtures explicitly in integration tests
- # -- they do not yet inherit this setting
fixtures :all
<% end -%>
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 1f704db510..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
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/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
index 0ba899176c..c338a0bdb1 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile
@@ -19,6 +19,10 @@ APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'
<% end %>
+<% if engine? -%>
+load 'rails/tasks/statistics.rake'
+<% end %>
+
<% unless options[:skip_gemspec] -%>
Bundler::GemHelper.install_tasks
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 908c4ce65e..49e5431a16 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -5,7 +5,7 @@ class Rails::InfoController < Rails::ApplicationController # :nodoc:
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
layout -> { request.xhr? ? false : 'application' }
- before_filter :require_local!
+ before_action :require_local!
def index
redirect_to action: :routes
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index dd318f52e5..32740d66da 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -3,8 +3,8 @@ require 'rails/application_controller'
class Rails::MailersController < Rails::ApplicationController # :nodoc:
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
- before_filter :require_local!
- before_filter :find_preview, only: :preview
+ before_action :require_local!
+ before_action :find_preview, only: :preview
def index
@previews = ActionMailer::Preview.all
@@ -70,4 +70,4 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
@email
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index 3b7f358a5b..df74643a59 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -2,7 +2,7 @@ if RUBY_VERSION < '1.9.3'
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
- Rails 4 prefers to run on Ruby 2.0.
+ Rails 4 prefers to run on Ruby 2.1 or newer.
You're running
#{desc}
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/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index c1674c72ad..ae5a7d2759 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -1,3 +1,6 @@
+# while having global constant is not good,
+# many 3rd party tools depend on it, like rspec-rails, cucumber-rails, etc
+# so if will be removed - deprecation warning is needed
STATS_DIRECTORIES = [
%w(Controllers app/controllers),
%w(Helpers app/helpers),
@@ -13,10 +16,12 @@ STATS_DIRECTORIES = [
%w(Integration\ tests test/integration),
%w(Functional\ tests\ (old) test/functional),
%w(Unit\ tests \ (old) test/unit)
-].collect { |name, dir| [ name, "#{Rails.root}/#{dir}" ] }.select { |name, dir| File.directory?(dir) }
+].collect do |name, dir|
+ [ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ]
+end.select { |name, dir| File.directory?(dir) }
-desc "Report code statistics (KLOCs, etc) from the application"
+desc "Report code statistics (KLOCs, etc) from the application or engine"
task :stats do
require 'rails/code_statistics'
CodeStatistics.new(*STATS_DIRECTORIES).to_s
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb
index 878b9b7930..75180ff978 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -1,4 +1,4 @@
-if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^test(?::|$)/).any?
+if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
ENV['RAILS_ENV'] ||= 'test'
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 51f55a560f..8f091cfdbf 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -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"
@@ -202,8 +203,6 @@ module ApplicationTests
app_file "app/assets/images/rails.png", "notactuallyapng"
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')
@@ -281,12 +277,9 @@ 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", "<%= asset_path('rails.png') %>"
- add_to_config "config.assets.compile = true"
- add_to_config "config.assets.digest = true"
- ENV["RAILS_ENV"] = nil
-
- 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
@@ -450,8 +446,9 @@ 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", "var src='<%= image_path('rails.png') %>';"
- add_to_config "config.assets.precompile = %w{image_loader.js}"
+ 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", "var src='<%= image_path('rails.png') %>';"
- add_to_config "config.assets.precompile = %w{app.js}"
+ 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 09aba1c2e9..207a0c7e86 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -8,6 +8,12 @@ end
class ::MyOtherMailInterceptor < ::MyMailInterceptor; end
+class ::MyPreviewMailInterceptor
+ def self.previewing_email(email); email; end
+end
+
+class ::MyOtherPreviewMailInterceptor < ::MyPreviewMailInterceptor; end
+
class ::MyMailObserver
def self.delivered_email(email); email; end
end
@@ -360,7 +366,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
@@ -460,6 +466,32 @@ module ApplicationTests
assert_equal [::MyMailInterceptor, ::MyOtherMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors")
end
+ test "registers preview interceptors with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.preview_interceptors = MyPreviewMailInterceptor
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ _ = ActionMailer::Base
+
+ assert_equal [::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ end
+
+ test "registers multiple preview interceptors with ActionMailer" do
+ add_to_config <<-RUBY
+ config.action_mailer.preview_interceptors = [MyPreviewMailInterceptor, "MyOtherPreviewMailInterceptor"]
+ RUBY
+
+ require "#{app_path}/config/environment"
+ require "mail"
+
+ _ = ActionMailer::Base
+
+ assert_equal [MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors
+ end
+
test "registers observers with ActionMailer" do
add_to_config <<-RUBY
config.action_mailer.observers = MyMailObserver
@@ -879,5 +911,21 @@ module ApplicationTests
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/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/application/test_test.rb b/railties/test/application/test_test.rb
index a223180169..c724c867ec 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -67,7 +67,7 @@ module ApplicationTests
assert_match %r{/app/test/unit/failing_test\.rb}, output
end
- test "migrations" do
+ test "ruby schema migrations" do
output = script('generate model user name:string')
version = output.match(/(\d+)_create_users\.rb/)[1]
@@ -104,6 +104,95 @@ module ApplicationTests
assert !result.include?("create_table(:users)")
end
+ test "sql structure migrations" do
+ output = script('generate model user name:string')
+ version = output.match(/(\d+)_create_users\.rb/)[1]
+
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ User.create! name: "Jon"
+ end
+ end
+ RUBY
+
+ app_file 'db/structure.sql', ''
+ app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY
+ Rails.application.config.active_record.schema_format = :sql
+ RUBY
+
+ assert_unsuccessful_run "models/user_test.rb", "Migrations are pending"
+
+ app_file 'db/structure.sql', <<-SQL
+ CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
+ CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");
+ CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
+ INSERT INTO schema_migrations (version) VALUES ('#{version}');
+ SQL
+
+ app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY
+ Rails.application.config.active_record.maintain_test_schema = false
+ RUBY
+
+ assert_unsuccessful_run "models/user_test.rb", "Could not find table 'users'"
+
+ File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb"
+
+ assert_successful_test_run('models/user_test.rb')
+ end
+
+ test "sql structure migrations when adding column to existing table" do
+ output_1 = script('generate model user name:string')
+ version_1 = output_1.match(/(\d+)_create_users\.rb/)[1]
+
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ User.create! name: "Jon"
+ end
+ end
+ RUBY
+
+ app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY
+ Rails.application.config.active_record.schema_format = :sql
+ RUBY
+
+ app_file 'db/structure.sql', <<-SQL
+ CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
+ CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");
+ CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
+ INSERT INTO schema_migrations (version) VALUES ('#{version_1}');
+ SQL
+
+ assert_successful_test_run('models/user_test.rb')
+
+ output_2 = script('generate migration add_email_to_users')
+ version_2 = output_2.match(/(\d+)_add_email_to_users\.rb/)[1]
+
+ app_file 'test/models/user_test.rb', <<-RUBY
+ require 'test_helper'
+
+ class UserTest < ActiveSupport::TestCase
+ test "user" do
+ User.create! name: "Jon", email: "jon@doe.com"
+ end
+ end
+ RUBY
+
+ app_file 'db/structure.sql', <<-SQL
+ CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
+ CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");
+ CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "email" varchar(255));
+ INSERT INTO schema_migrations (version) VALUES ('#{version_1}');
+ INSERT INTO schema_migrations (version) VALUES ('#{version_2}');
+ SQL
+
+ assert_successful_test_run('models/user_test.rb')
+ end
+
private
def assert_unsuccessful_run(name, message)
result = run_test_file(name)
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 0db40c1d32..6d6de0fb52 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -41,13 +41,13 @@ class ActionsTest < Rails::Generators::TestCase
def test_add_source_adds_source_to_gemfile
run_generator
action :add_source, 'http://gems.github.com'
- assert_file 'Gemfile', /source "http:\/\/gems\.github\.com"/
+ assert_file 'Gemfile', /source 'http:\/\/gems\.github\.com'/
end
def test_gem_should_put_gem_dependency_in_gemfile
run_generator
action :gem, 'will-paginate'
- assert_file 'Gemfile', /gem "will\-paginate"/
+ assert_file 'Gemfile', /gem 'will\-paginate'/
end
def test_gem_with_version_should_include_version_in_gemfile
@@ -55,7 +55,7 @@ class ActionsTest < Rails::Generators::TestCase
action :gem, 'rspec', '>=2.0.0.a5'
- assert_file 'Gemfile', /gem "rspec", ">=2.0.0.a5"/
+ assert_file 'Gemfile', /gem 'rspec', '>=2.0.0.a5'/
end
def test_gem_should_insert_on_separate_lines
@@ -66,8 +66,8 @@ class ActionsTest < Rails::Generators::TestCase
action :gem, 'rspec'
action :gem, 'rspec-rails'
- assert_file 'Gemfile', /^gem "rspec"$/
- assert_file 'Gemfile', /^gem "rspec-rails"$/
+ assert_file 'Gemfile', /^gem 'rspec'$/
+ assert_file 'Gemfile', /^gem 'rspec-rails'$/
end
def test_gem_should_include_options
@@ -75,7 +75,15 @@ class ActionsTest < Rails::Generators::TestCase
action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1'
- assert_file 'Gemfile', /gem "rspec", github: "dchelimsky\/rspec", tag: "1\.2\.9\.rc1"/
+ assert_file 'Gemfile', /gem 'rspec', github: 'dchelimsky\/rspec', tag: '1\.2\.9\.rc1'/
+ end
+
+ def test_gem_falls_back_to_inspect_if_string_contains_single_quote
+ run_generator
+
+ action :gem, 'rspec', ">=2.0'0"
+
+ assert_file 'Gemfile', /^gem 'rspec', ">=2\.0'0"$/
end
def test_gem_group_should_wrap_gems_in_a_group
@@ -89,7 +97,7 @@ class ActionsTest < Rails::Generators::TestCase
gem 'fakeweb'
end
- assert_file 'Gemfile', /\ngroup :development, :test do\n gem "rspec-rails"\nend\n\ngroup :test do\n gem "fakeweb"\nend/
+ assert_file 'Gemfile', /\ngroup :development, :test do\n gem 'rspec-rails'\nend\n\ngroup :test do\n gem 'fakeweb'\nend/
end
def test_environment_should_include_data_in_environment_initializer_block
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 007dd886da..74cff08676 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -21,6 +21,7 @@ DEFAULT_APP_FILES = %w(
bin/bundle
bin/rails
bin/rake
+ bin/setup
config/environments
config/initializers
config/locales
@@ -119,7 +120,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 +135,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!/
diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb
index a94350cbd7..31c2d846e2 100644
--- a/railties/test/generators/argv_scrubber_test.rb
+++ b/railties/test/generators/argv_scrubber_test.rb
@@ -1,5 +1,5 @@
-require 'active_support/test_case'
require 'active_support/testing/autorun'
+require 'active_support/test_case'
require 'rails/generators/rails/app/app_generator'
require 'tempfile'
@@ -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/generator_test.rb b/railties/test/generators/generator_test.rb
index 7871399dd7..b136239795 100644
--- a/railties/test/generators/generator_test.rb
+++ b/railties/test/generators/generator_test.rb
@@ -1,5 +1,5 @@
-require 'active_support/test_case'
require 'active_support/testing/autorun'
+require 'active_support/test_case'
require 'rails/generators/app_base'
module Rails
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 853af80111..7180efee41 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -312,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
@@ -323,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
@@ -371,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) }
@@ -382,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/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 8e198d5fe1..b998fef42e 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -78,9 +78,12 @@ module SharedGeneratorTests
end
def test_template_raises_an_error_with_invalid_path
- content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) }
- assert_match(/The template \[.*\] could not be loaded/, content)
- assert_match(/non\/existent\/path/, content)
+ quietly do
+ content = capture(:stderr){ run_generator([destination_root, "-m", "non/existent/path"]) }
+
+ assert_match(/The template \[.*\] could not be loaded/, content)
+ assert_match(/non\/existent\/path/, content)
+ end
end
def test_template_is_executed_when_supplied
@@ -89,7 +92,7 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
+ quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
end
def test_template_is_executed_when_supplied_an_https_path
@@ -98,7 +101,7 @@ module SharedGeneratorTests
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], template: path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
+ quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
end
def test_dev_option
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index eac28badfe..0b4edafaca 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -21,8 +21,16 @@ class GeneratorsTest < Rails::Generators::TestCase
end
def test_invoke_when_generator_is_not_found
- output = capture(:stdout){ Rails::Generators.invoke :unknown }
- assert_equal "Could not find generator unknown.\n", output
+ name = :unknown
+ output = capture(:stdout){ Rails::Generators.invoke name }
+ assert_match "Could not find generator '#{name}'", output
+ assert_match "`rails generate --help`", output
+ end
+
+ def test_generator_suggestions
+ name = :migrationz
+ output = capture(:stdout){ Rails::Generators.invoke name }
+ assert_match "Maybe you meant 'migration'", output
end
def test_help_when_a_generator_with_required_arguments_is_invoked_without_arguments
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 44a5fd1904..4bec302ff8 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -66,16 +66,6 @@ class InfoTest < ActiveSupport::TestCase
end
protected
- def svn_info=(info)
- Rails::Info.module_eval do
- class << self
- def svn_info
- info
- end
- end
- end
- end
-
def properties
Rails::Info.properties
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index c4b18e9ea5..ec64ce5941 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'
@@ -111,6 +112,38 @@ module RailtiesTest
end
end
+ test 'respects the order of railties when installing migrations' do
+ @blog = engine "blog" do |plugin|
+ plugin.write "lib/blog.rb", <<-RUBY
+ module Blog
+ class Engine < ::Rails::Engine
+ end
+ end
+ RUBY
+ end
+
+ @plugin.write "db/migrate/1_create_users.rb", <<-RUBY
+ class CreateUsers < ActiveRecord::Migration
+ end
+ RUBY
+
+ @blog.write "db/migrate/2_create_blogs.rb", <<-RUBY
+ class CreateBlogs < ActiveRecord::Migration
+ end
+ RUBY
+
+ add_to_config("config.railties_order = [Bukkits::Engine, Blog::Engine, :all, :main_app]")
+
+ boot_rails
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake railties:install:migrations`.split("\n")
+
+ assert_match(/Copied migration \d+_create_users.bukkits.rb from bukkits/, output.first)
+ assert_match(/Copied migration \d+_create_blogs.blog_engine.rb from blog_engine/, output.last)
+ end
+ end
+
test "mountable engine should copy migrations within engine_path" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
@@ -592,11 +625,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 +741,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 +1113,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