aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael Mendonça França <rafaelmfranca@gmail.com>2014-08-12 11:10:42 -0300
committerRafael Mendonça França <rafaelmfranca@gmail.com>2014-08-12 11:10:42 -0300
commita2400308eab88b5eff27e05d1f7624345fb33b54 (patch)
tree38630de37ac59c50061734d9f353ebb798ba2023
parent045d7173167043389dbe8bd961425c1b1cc86d48 (diff)
parent82e28492e7c581cdeea904464a18eb11118f4ac0 (diff)
downloadrails-a2400308eab88b5eff27e05d1f7624345fb33b54.tar.gz
rails-a2400308eab88b5eff27e05d1f7624345fb33b54.tar.bz2
rails-a2400308eab88b5eff27e05d1f7624345fb33b54.zip
Merge branch 'master' into loofah
Conflicts: actionpack/CHANGELOG.md actionpack/test/controller/integration_test.rb actionview/CHANGELOG.md
-rw-r--r--.travis.yml3
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--Gemfile8
-rw-r--r--README.md2
-rw-r--r--actionmailer/CHANGELOG.md13
-rw-r--r--actionmailer/lib/action_mailer/base.rb24
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb24
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb7
-rw-r--r--actionmailer/test/base_test.rb39
-rw-r--r--actionpack/CHANGELOG.md42
-rw-r--r--actionpack/lib/abstract_controller/base.rb8
-rw-r--r--actionpack/lib/abstract_controller/railties/routes_helpers.rb6
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb51
-rw-r--r--actionpack/lib/action_controller/metal.rb3
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb2
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb10
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb9
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb8
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb15
-rw-r--r--actionpack/lib/action_controller/test_case.rb57
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb69
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb6
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb54
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y22
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb35
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb17
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb40
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb44
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb25
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb126
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb237
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb7
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb4
-rw-r--r--actionpack/test/abstract_unit.rb153
-rw-r--r--actionpack/test/controller/integration_test.rb3
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_body_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_html_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_plain_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb8
-rw-r--r--actionpack/test/controller/redirect_test.rb10
-rw-r--r--actionpack/test/controller/routing_test.rb285
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb2
-rw-r--r--actionpack/test/controller/test_case_test.rb11
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb81
-rw-r--r--actionpack/test/controller/url_for_test.rb8
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb2
-rw-r--r--actionpack/test/dispatch/mapper_test.rb4
-rw-r--r--actionpack/test/dispatch/mount_test.rb14
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb129
-rw-r--r--actionpack/test/dispatch/request_test.rb43
-rw-r--r--actionpack/test/dispatch/routing_test.rb8
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb17
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb3
-rw-r--r--actionpack/test/dispatch/template_assertions_test.rb98
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb6
-rw-r--r--actionpack/test/fixtures/test/_changing_priority.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_changing_priority.json.erb1
-rw-r--r--actionpack/test/fixtures/test/_counter.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_customer.erb1
-rw-r--r--actionpack/test/fixtures/test/_customer_counter.erb1
-rw-r--r--actionpack/test/fixtures/test/_customer_counter_with_as.erb1
-rw-r--r--actionpack/test/fixtures/test/_customer_greeting.erb1
-rw-r--r--actionpack/test/fixtures/test/_customer_with_var.erb1
-rw-r--r--actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_first_json_partial.json.erb1
-rw-r--r--actionpack/test/fixtures/test/_form.erb1
-rw-r--r--actionpack/test/fixtures/test/_hash_greeting.erb1
-rw-r--r--actionpack/test/fixtures/test/_hash_object.erb2
-rw-r--r--actionpack/test/fixtures/test/_hello.builder1
-rw-r--r--actionpack/test/fixtures/test/_json_change_priority.json.erb0
-rw-r--r--actionpack/test/fixtures/test/_labelling_form.erb1
-rw-r--r--actionpack/test/fixtures/test/_layout_for_partial.html.erb3
-rw-r--r--actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_partial_html_erb.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_partial_name_local_variable.erb1
-rw-r--r--actionpack/test/fixtures/test/_partial_only.erb1
-rw-r--r--actionpack/test/fixtures/test/_partial_only_html.html1
-rw-r--r--actionpack/test/fixtures/test/_partial_with_partial.erb2
-rw-r--r--actionpack/test/fixtures/test/_person.erb2
-rw-r--r--actionpack/test/fixtures/test/_raise_indentation.html.erb13
-rw-r--r--actionpack/test/fixtures/test/_second_json_partial.json.erb1
-rw-r--r--actionpack/test/fixtures/test/action_talk_to_layout.erb2
-rw-r--r--actionpack/test/fixtures/test/calling_partial_with_layout.html.erb1
-rw-r--r--actionpack/test/fixtures/test/capturing.erb4
-rw-r--r--actionpack/test/fixtures/test/change_priority.html.erb2
-rw-r--r--actionpack/test/fixtures/test/content_for.erb1
-rw-r--r--actionpack/test/fixtures/test/content_for_concatenated.erb3
-rw-r--r--actionpack/test/fixtures/test/content_for_with_parameter.erb2
-rw-r--r--actionpack/test/fixtures/test/formatted_html_erb.html.erb1
-rw-r--r--actionpack/test/fixtures/test/greeting.html.erb1
-rw-r--r--actionpack/test/fixtures/test/greeting.xml.erb1
-rw-r--r--actionpack/test/fixtures/test/hello,world.erb1
-rw-r--r--actionpack/test/fixtures/test/hello.builder4
-rw-r--r--actionpack/test/fixtures/test/hello_world_container.builder3
-rw-r--r--actionpack/test/fixtures/test/hello_world_from_rxml.builder3
-rw-r--r--actionpack/test/fixtures/test/hello_world_with_layout_false.erb1
-rw-r--r--actionpack/test/fixtures/test/html_template.html.erb1
-rw-r--r--actionpack/test/fixtures/test/hyphen-ated.erb1
-rw-r--r--actionpack/test/fixtures/test/list.erb1
-rw-r--r--actionpack/test/fixtures/test/non_erb_block_content_for.builder4
-rw-r--r--actionpack/test/fixtures/test/potential_conflicts.erb4
-rw-r--r--actionpack/test/fixtures/test/proper_block_detection.erb1
-rw-r--r--actionpack/test/fixtures/test/render_file_from_template.html.erb1
-rw-r--r--actionpack/test/fixtures/test/render_file_with_locals_and_default.erb1
-rw-r--r--actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb1
-rw-r--r--actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb1
-rw-r--r--actionpack/test/fixtures/test/render_implicit_js_template_without_layout.js.erb1
-rw-r--r--actionpack/test/fixtures/test/render_partial_inside_directory.html.erb1
-rw-r--r--actionpack/test/fixtures/test/render_to_string_test.erb1
-rw-r--r--actionpack/test/fixtures/test/render_two_partials.html.erb2
-rw-r--r--actionpack/test/fixtures/test/using_layout_around_block.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_html_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_partial.text.erb1
-rw-r--r--actionpack/test/fixtures/test/with_xml_template.html.erb1
-rw-r--r--actionpack/test/routing/helper_test.rb14
-rw-r--r--actionview/CHANGELOG.md21
-rw-r--r--actionview/lib/action_view/base.rb9
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb14
-rw-r--r--actionview/lib/action_view/digestor.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb170
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb2
-rw-r--r--actionview/lib/action_view/log_subscriber.rb10
-rw-r--r--actionview/lib/action_view/lookup_context.rb5
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb6
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb100
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb9
-rw-r--r--actionview/lib/action_view/rendering.rb5
-rw-r--r--actionview/lib/action_view/test_case.rb2
-rw-r--r--actionview/test/abstract_unit.rb7
-rw-r--r--actionview/test/actionpack/controller/render_test.rb22
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb12
-rw-r--r--actionview/test/fixtures/actionpack/test/_customer_iteration.erb1
-rw-r--r--actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb8
-rw-r--r--actionview/test/template/dependency_tracker_test.rb15
-rw-r--r--actionview/test/template/form_helper_test.rb7
-rw-r--r--actionview/test/template/form_options_helper_test.rb13
-rw-r--r--actionview/test/template/partial_iteration_test.rb33
-rw-r--r--actionview/test/template/render_test.rb17
-rw-r--r--actionview/test/template/test_case_test.rb3
-rw-r--r--activemodel/CHANGELOG.md26
-rw-r--r--activemodel/lib/active_model/dirty.rb34
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb6
-rw-r--r--activemodel/lib/active_model/secure_password.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb6
-rw-r--r--activemodel/lib/active_model/validator.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb43
-rw-r--r--activemodel/test/cases/helper.rb7
-rw-r--r--activemodel/test/cases/secure_password_test.rb10
-rw-r--r--activemodel/test/cases/validations_test.rb37
-rw-r--r--activemodel/test/models/automobile.rb13
-rw-r--r--activerecord/CHANGELOG.md167
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations.rb24
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb7
-rw-r--r--activerecord/lib/active_record/attribute_set.rb6
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/coders/json.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb214
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb38
-rw-r--r--activerecord/lib/active_record/core.rb5
-rw-r--r--activerecord/lib/active_record/counter_cache.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb4
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb10
-rw-r--r--activerecord/lib/active_record/migration.rb12
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb2
-rw-r--r--activerecord/lib/active_record/migration/join_table.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb11
-rw-r--r--activerecord/lib/active_record/persistence.rb20
-rw-r--r--activerecord/lib/active_record/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake19
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb1
-rw-r--r--activerecord/lib/active_record/reflection.rb260
-rw-r--r--activerecord/lib/active_record/relation.rb5
-rw-r--r--activerecord/lib/active_record/relation/batches.rb1
-rw-r--r--activerecord/lib/active_record/schema.rb1
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb27
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb33
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/timestamp.rb1
-rw-r--r--activerecord/lib/active_record/type/decimal.rb21
-rw-r--r--activerecord/lib/active_record/type/serialized.rb6
-rw-r--r--activerecord/lib/active_record/validations.rb15
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/test/cases/adapter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb7
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb7
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb34
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb62
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb2
-rw-r--r--activerecord/test/cases/attribute_decorators_test.rb1
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb6
-rw-r--r--activerecord/test/cases/attribute_set_test.rb10
-rw-r--r--activerecord/test/cases/attribute_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb22
-rw-r--r--activerecord/test/cases/dirty_test.rb16
-rw-r--r--activerecord/test/cases/helper.rb7
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb14
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb154
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb24
-rw-r--r--activerecord/test/cases/migration/pending_migrations_test.rb4
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb5
-rw-r--r--activerecord/test/cases/migration_test.rb12
-rw-r--r--activerecord/test/cases/primary_keys_test.rb10
-rw-r--r--activerecord/test/cases/reflection_test.rb64
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb37
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb13
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb6
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb2
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb2
-rw-r--r--activerecord/test/cases/test_case.rb17
-rw-r--r--activerecord/test/cases/transactions_test.rb28
-rw-r--r--activerecord/test/cases/type/decimal_test.rb38
-rw-r--r--activerecord/test/cases/types_test.rb7
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb5
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb1
-rw-r--r--activerecord/test/models/face.rb3
-rw-r--r--activerecord/test/models/man.rb2
-rw-r--r--activerecord/test/schema/schema.rb9
-rw-r--r--activesupport/CHANGELOG.md45
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/digest/uuid.rb51
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/itself.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb61
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_query.rb64
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb47
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb24
-rw-r--r--activesupport/lib/active_support/file_watcher.rb36
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb7
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb7
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb3
-rw-r--r--activesupport/lib/active_support/test_case.rb18
-rw-r--r--activesupport/lib/active_support/testing/tagged_logging.rb2
-rw-r--r--activesupport/test/abstract_unit.rb7
-rw-r--r--activesupport/test/clean_backtrace_test.rb5
-rw-r--r--activesupport/test/core_ext/array/access_test.rb30
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb197
-rw-r--r--activesupport/test/core_ext/array/extract_options_test.rb45
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb126
-rw-r--r--activesupport/test/core_ext/array/prepend_append_test.rb12
-rw-r--r--activesupport/test/core_ext/array/wrap_test.rb77
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb482
-rw-r--r--activesupport/test/core_ext/digest/uuid_test.rb24
-rw-r--r--activesupport/test/core_ext/duration_test.rb4
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb32
-rw-r--r--activesupport/test/core_ext/hash/transform_values_test.rb12
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb35
-rw-r--r--activesupport/test/core_ext/kernel_test.rb36
-rw-r--r--activesupport/test/core_ext/load_error_test.rb11
-rw-r--r--activesupport/test/core_ext/object/acts_like_test.rb33
-rw-r--r--activesupport/test/core_ext/object/instance_variables_test.rb31
-rw-r--r--activesupport/test/core_ext/object/itself_test.rb9
-rw-r--r--activesupport/test/core_ext/object/json_cherry_pick_test.rb42
-rw-r--r--activesupport/test/core_ext/object/to_param_test.rb18
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb14
-rw-r--r--activesupport/test/core_ext/object/try_test.rb (renamed from activesupport/test/core_ext/object_and_class_ext_test.rb)67
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/securerandom_test.rb28
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb33
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb1
-rw-r--r--activesupport/test/core_ext/uri_ext_test.rb2
-rw-r--r--activesupport/test/deprecation_test.rb17
-rw-r--r--activesupport/test/key_generator_test.rb30
-rw-r--r--activesupport/test/multibyte_conformance_test.rb12
-rw-r--r--activesupport/test/multibyte_proxy_test.rb34
-rw-r--r--activesupport/test/option_merger_test.rb9
-rw-r--r--activesupport/test/subscriber_test.rb2
-rw-r--r--guides/bug_report_templates/action_controller_master.rb2
-rw-r--r--guides/bug_report_templates/active_record_master.rb2
-rw-r--r--guides/source/4_2_release_notes.md296
-rw-r--r--guides/source/action_mailer_basics.md16
-rw-r--r--guides/source/active_model_basics.md4
-rw-r--r--guides/source/active_record_basics.md9
-rw-r--r--guides/source/active_record_querying.md213
-rw-r--r--guides/source/active_record_validations.md4
-rw-r--r--guides/source/asset_pipeline.md12
-rw-r--r--guides/source/association_basics.md31
-rw-r--r--guides/source/command_line.md5
-rw-r--r--guides/source/configuring.md25
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md6
-rw-r--r--guides/source/engines.md8
-rw-r--r--guides/source/generators.md6
-rw-r--r--guides/source/getting_started.md26
-rw-r--r--guides/source/plugins.md4
-rw-r--r--guides/source/rails_application_templates.md24
-rw-r--r--guides/source/routing.md4
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md22
-rw-r--r--guides/source/testing.md23
-rw-r--r--guides/source/upgrading_ruby_on_rails.md41
-rw-r--r--railties/CHANGELOG.md49
-rw-r--r--railties/lib/rails.rb8
-rw-r--r--railties/lib/rails/app_rails_loader.rb6
-rw-r--r--railties/lib/rails/application.rb67
-rw-r--r--railties/lib/rails/application/configuration.rb14
-rw-r--r--railties/lib/rails/commands/server.rb18
-rw-r--r--railties/lib/rails/engine.rb4
-rw-r--r--railties/lib/rails/generators/actions.rb11
-rw-r--r--railties/lib/rails/generators/actions/create_migration.rb3
-rw-r--r--railties/lib/rails/generators/app_base.rb22
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile18
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/controller/USAGE1
-rw-r--r--railties/lib/rails/generators/rails/helper/USAGE4
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE12
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/USAGE8
-rw-r--r--railties/lib/rails/generators/test_unit/helper/helper_generator.rb6
-rw-r--r--railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb6
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb17
-rw-r--r--railties/lib/rails/rack/logger.rb2
-rw-r--r--railties/test/abstract_unit.rb27
-rw-r--r--railties/test/app_rails_loader_test.rb31
-rw-r--r--railties/test/application/configuration/base_test.rb37
-rw-r--r--railties/test/application/configuration/custom_test.rb15
-rw-r--r--railties/test/application/configuration_test.rb164
-rw-r--r--railties/test/application/initializers/frameworks_test.rb4
-rw-r--r--railties/test/application/initializers/i18n_test.rb20
-rw-r--r--railties/test/application/mailer_previews_test.rb52
-rw-r--r--railties/test/application/middleware_test.rb2
-rw-r--r--railties/test/application/multiple_applications_test.rb62
-rw-r--r--railties/test/application/rack/logger_test.rb8
-rw-r--r--railties/test/application/test_test.rb91
-rw-r--r--railties/test/commands/console_test.rb62
-rw-r--r--railties/test/commands/dbconsole_test.rb112
-rw-r--r--railties/test/engine_test.rb11
-rw-r--r--railties/test/generators/actions_test.rb3
-rw-r--r--railties/test/generators/app_generator_test.rb76
-rw-r--r--railties/test/generators/argv_scrubber_test.rb2
-rw-r--r--railties/test/generators/controller_generator_test.rb2
-rw-r--r--railties/test/generators/generator_test.rb2
-rw-r--r--railties/test/generators/generators_test_helper.rb10
-rw-r--r--railties/test/generators/helper_generator_test.rb15
-rw-r--r--railties/test/generators/named_base_test.rb1
-rw-r--r--railties/test/generators/namespaced_generators_test.rb7
-rw-r--r--railties/test/generators/plugin_generator_test.rb27
-rw-r--r--railties/test/generators/resource_generator_test.rb1
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb2
-rw-r--r--railties/test/generators/scaffold_generator_test.rb4
-rw-r--r--railties/test/generators_test.rb1
-rw-r--r--railties/test/isolation/abstract_unit.rb27
-rw-r--r--railties/test/path_generation_test.rb88
-rw-r--r--railties/test/paths_test.rb1
-rw-r--r--railties/test/rack_logger_test.rb4
-rw-r--r--railties/test/rails_info_controller_test.rb1
415 files changed, 5699 insertions, 3045 deletions
diff --git a/.travis.yml b/.travis.yml
index 8eee7b129b..43b08044d3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,7 +11,8 @@ rvm:
- jruby
env:
- "GEM=railties"
- - "GEM=ap,am,amo,as,av"
+ - "GEM=ap"
+ - "GEM=am,amo,as,av"
- "GEM=ar:mysql"
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 617f0af480..9ba2e53ef2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,5 +12,6 @@ Ruby on Rails is a volunteer effort. We encourage you to pitch in. [Join the tea
* If you have a change or new feature in mind, please [suggest it on the rubyonrails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core) and start writing code.
-Thanks! :heart: :heart: :heart:
+Thanks! :heart: :heart: :heart:
+
Rails Team
diff --git a/Gemfile b/Gemfile
index 29ccd10e6d..64db84b7e6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -13,7 +13,7 @@ gem 'mocha', '~> 0.14', require: false
gem 'rack', github: 'rack/rack'
gem 'rack-cache', '~> 1.2'
gem 'jquery-rails', '~> 3.1.0'
-gem 'turbolinks'
+gem 'turbolinks', github: 'rails/turbolinks', branch: 'master'
gem 'coffee-rails', '~> 4.0.0'
gem 'arel', github: 'rails/arel', branch: 'master'
gem 'rails-dom-testing', github: 'rails/rails-dom-testing'
@@ -92,6 +92,12 @@ platforms :jruby do
end
end
+platforms :rbx do
+ # The rubysl-yaml gem doesn't ship with Psych by default
+ # as it needs libyaml that isn't always available.
+ gem 'psych', '~> 2.0'
+end
+
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED']
platforms :ruby do
diff --git a/README.md b/README.md
index 6a73727eed..d786914d6d 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ independently outside Rails.
* [Getting Started with Rails](http://guides.rubyonrails.org/getting_started.html)
* [Ruby on Rails Guides](http://guides.rubyonrails.org)
* [The API Documentation](http://api.rubyonrails.org)
- * [Ruby on Rails Tutorial](http://ruby.railstutorial.org/ruby-on-rails-tutorial-book)
+ * [Ruby on Rails Tutorial](http://www.railstutorial.org/book)
## Contributing
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 4e3a9daf7d..ab93745f60 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,16 @@
+* Deprecate `*_path` helpers in email views. When used they generate
+ non-working links and are not the intention of most developers. Instead
+ we recommend to use `*_url` helper.
+
+ *Richard Schneeman*
+
+* Raise an exception when attachments are added after `mail` was called.
+ This is a safeguard to prevent invalid emails.
+
+ Fixes #16163.
+
+ *Yves Senn*
+
* Add `config.action_mailer.show_previews` configuration option.
This config option can be used to enable the mail preview in environments
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 135cf35ac0..bc540aece0 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -394,7 +394,7 @@ module ActionMailer
# implement for a custom delivery agent.
#
# * <tt>perform_deliveries</tt> - Determines whether emails are actually sent from Action Mailer when you
- # call <tt>.deliver</tt> on an mail message or on an Action Mailer method. This is on by default but can
+ # call <tt>.deliver</tt> on an email message or on an Action Mailer method. This is on by default but can
# be turned off to aid in functional testing.
#
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
@@ -649,7 +649,22 @@ module ActionMailer
# mail.attachments[0] # => Mail::Part (first attachment)
#
def attachments
- @_message.attachments
+ if @_mail_was_called
+ LateAttachmentsProxy.new(@_message.attachments)
+ else
+ @_message.attachments
+ end
+ end
+
+ class LateAttachmentsProxy < SimpleDelegator
+ def inline; _raise_error end
+ def []=(_name, _content); _raise_error end
+
+ private
+ def _raise_error
+ raise RuntimeError, "Can't add attachments after `mail` was called.\n" \
+ "Make sure to use `attachments[]=` before calling `mail`."
+ end
end
# The main method that creates the message and renders the email templates. There are
@@ -882,6 +897,11 @@ module ActionMailer
container.add_part(part)
end
+ # Emails do not support relative path links.
+ def self.supports_path?
+ false
+ end
+
ActiveSupport.run_load_hooks(:action_mailer, self)
end
end
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index e4aaab34b1..5b57c75ec3 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -6,25 +6,27 @@ module ActionMailer
class LogSubscriber < ActiveSupport::LogSubscriber
# An email was delivered.
def deliver(event)
- return unless logger.info?
- recipients = Array(event.payload[:to]).join(', ')
- info("\nSent mail to #{recipients} (#{event.duration.round(1)}ms)")
- debug(event.payload[:mail])
+ info do
+ recipients = Array(event.payload[:to]).join(', ')
+ "\nSent mail to #{recipients} (#{event.duration.round(1)}ms)"
+ end
+
+ debug { event.payload[:mail] }
end
# An email was received.
def receive(event)
- return unless logger.info?
- info("\nReceived mail (#{event.duration.round(1)}ms)")
- debug(event.payload[:mail])
+ info { "\nReceived mail (#{event.duration.round(1)}ms)" }
+ debug { event.payload[:mail] }
end
# An email was generated.
def process(event)
- return unless logger.debug?
- mailer = event.payload[:mailer]
- action = event.payload[:action]
- debug("\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms")
+ debug do
+ mailer = event.payload[:mailer]
+ action = event.payload[:action]
+ "\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms"
+ end
end
# Use the logger configured for ActionMailer::Base
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 6f760732e2..c62d4b5082 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -30,7 +30,7 @@ module ActionMailer
ActiveSupport.on_load(:action_mailer) do
include AbstractController::UrlFor
- extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
+ extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false)
include app.routes.mounted_helpers
register_interceptors(options.delete(:interceptors))
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 2e5242f0b1..28727b62ec 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -49,3 +49,10 @@ end
def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 229ded8e04..6116d1e29f 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -232,6 +232,45 @@ class BaseTest < ActiveSupport::TestCase
end
end
+ test "adding attachments after mail was called raises exception" do
+ class LateAttachmentMailer < ActionMailer::Base
+ def welcome
+ mail body: "yay", from: "welcome@example.com", to: "to@example.com"
+ attachments['invoice.pdf'] = 'This is test File content'
+ end
+ end
+
+ e = assert_raises(RuntimeError) { LateAttachmentMailer.welcome }
+ assert_match(/Can't add attachments after `mail` was called./, e.message)
+ end
+
+ test "adding inline attachments after mail was called raises exception" do
+ class LateInlineAttachmentMailer < ActionMailer::Base
+ def welcome
+ mail body: "yay", from: "welcome@example.com", to: "to@example.com"
+ attachments.inline['invoice.pdf'] = 'This is test File content'
+ end
+ end
+
+ e = assert_raises(RuntimeError) { LateInlineAttachmentMailer.welcome }
+ assert_match(/Can't add attachments after `mail` was called./, e.message)
+ end
+
+ test "accessing attachments works after mail was called" do
+ class LateAttachmentAccessorMailer < ActionMailer::Base
+ def welcome
+ attachments['invoice.pdf'] = 'This is test File content'
+ mail body: "yay", from: "welcome@example.com", to: "to@example.com"
+
+ unless attachments.map(&:filename) == ["invoice.pdf"]
+ raise Minitest::Assertion, "Should allow access to attachments"
+ end
+ end
+ end
+
+ assert_nothing_raised { LateAttachmentAccessorMailer.welcome }
+ end
+
# Implicit multipart
test "implicit multipart" do
email = BaseMailer.implicit_multipart
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index f3cfedef4f..af1334cff6 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -4,6 +4,46 @@
*Kasper Timm Hansen*
+* Extract source code for the entire exception stack trace for
+ better debugging and diagnosis.
+
+ *Ryan Dao*
+
+* Allows ActionDispatch::Request::LOCALHOST to match any IPv4 127.0.0.0/8
+ loopback address.
+
+ *Earl St Sauver*, *Sven Riedel*
+
+* Preserve original path in `ShowExceptions` middleware by stashing it as
+ `env["action_dispatch.original_path"]`
+
+ `ActionDispatch::ShowExceptions` overwrites `PATH_INFO` with the status code
+ for the exception defined in `ExceptionWrapper`, so the path
+ the user was visiting when an exception occurred was not previously
+ available to any custom exceptions_app. The original `PATH_INFO` is now
+ stashed in `env["action_dispatch.original_path"]`.
+
+ *Grey Baker*
+
+* Use `String#bytesize` instead of `String#size` when checking for cookie
+ overflow.
+
+ *Agis Anastasopoulos*
+
+* `render nothing: true` or rendering a `nil` body no longer add a single
+ space to the response body.
+
+ The old behavior was added as a workaround for a bug in an early version of
+ Safari, where the HTTP headers are not returned correctly if the response
+ body has a 0-length. This is been fixed since and the workaround is no
+ longer necessary.
+
+ Use `render body: ' '` if the old behavior is desired.
+
+ See #14883 for details.
+
+ *Godfrey Chan*
+
* Prepend a JS comment to JSONP callbacks. Addresses CVE-2014-4671
("Rosetta Flash")
@@ -65,6 +105,8 @@
application. Use of a symbol should be replaced with `action: symbol`.
Use of a string without a "#" should be replaced with `controller: string`.
+ *Aaron Patterson*
+
* Fix URL generation with `:trailing_slash` such that it does not add
a trailing slash after `.:format`
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 15faabf977..4026dab2ce 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -164,6 +164,14 @@ module AbstractController
_find_action_name(action_name).present?
end
+ # Returns true if the given controller is capable of rendering
+ # a path. A subclass of +AbstractController::Base+
+ # may return false. An Email controller for example does not
+ # support paths, only full URLs.
+ def self.supports_path?
+ true
+ end
+
private
# Returns true if the name can be considered an action because
diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
index 6684f46f64..568c47e43a 100644
--- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb
+++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
@@ -1,14 +1,14 @@
module AbstractController
module Railties
module RoutesHelpers
- def self.with(routes)
+ def self.with(routes, include_path_helpers = true)
Module.new do
define_method(:inherited) do |klass|
super(klass)
if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
- klass.send(:include, namespace.railtie_routes_url_helpers)
+ klass.send(:include, namespace.railtie_routes_url_helpers(include_path_helpers))
else
- klass.send(:include, routes.url_helpers)
+ klass.send(:include, routes.url_helpers(include_path_helpers))
end
end
end
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index b1acca2435..89fa75f025 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -16,50 +16,51 @@ module ActionController
end
def process_action(event)
- return unless logger.info?
-
- payload = event.payload
- additions = ActionController::Base.log_process_action(payload)
-
- status = payload[:status]
- if status.nil? && payload[:exception].present?
- exception_class_name = payload[:exception].first
- status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
+ info do
+ payload = event.payload
+ additions = ActionController::Base.log_process_action(payload)
+
+ status = payload[:status]
+ if status.nil? && payload[:exception].present?
+ exception_class_name = payload[:exception].first
+ status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
+ end
+ message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
+ message << " (#{additions.join(" | ")})" unless additions.blank?
+ message
end
- message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ")})" unless additions.blank?
-
- info(message)
end
def halted_callback(event)
- info("Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected")
+ info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" }
end
def send_file(event)
- info("Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)")
+ info { "Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)" }
end
def redirect_to(event)
- info("Redirected to #{event.payload[:location]}")
+ info { "Redirected to #{event.payload[:location]}" }
end
def send_data(event)
- info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)")
+ info { "Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)" }
end
def unpermitted_parameters(event)
- unpermitted_keys = event.payload[:keys]
- debug("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}")
+ debug do
+ unpermitted_keys = event.payload[:keys]
+ "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
+ end
end
def deep_munge(event)
- message = "Value for params[:#{event.payload[:keys].join('][:')}] was set "\
- "to nil, because it was one of [], [null] or [null, null, ...]. "\
- "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\
- "for more information."\
-
- debug(message)
+ debug do
+ "Value for params[:#{event.payload[:keys].join('][:')}] was set "\
+ "to nil, because it was one of [], [null] or [null, null, ...]. "\
+ "Go to http://guides.rubyonrails.org/security.html#unsafe-query-generation "\
+ "for more information."\
+ end
end
%w(write_fragment read_fragment exist_fragment?
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 9a427ebfdb..bfbc15a901 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -182,7 +182,8 @@ module ActionController
body = [body] unless body.nil? || body.respond_to?(:each)
super
end
-
+
+ # Tests if render or redirect has already happened.
def performed?
response_body || (response && response.committed?)
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index a2cb6d1e66..d920668184 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -85,7 +85,7 @@ module ActionController
if host_or_options.is_a?(Hash)
options.merge!(host_or_options)
elsif host_or_options
- options.merge!(:host => host_or_options)
+ options[:host] = host_or_options
end
secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 84a9112144..3d2badf9c2 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -14,6 +14,8 @@ module ActionController
# return head(:method_not_allowed) unless request.post?
# return head(:bad_request) unless valid_request?
# render
+ #
+ # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
def head(status, options = {})
options, status = status, nil if status.is_a?(Hash)
status ||= options.delete(:status) || :ok
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 706ce04062..c9ef3a3dad 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -303,10 +303,12 @@ module ActionController
logger = ActionController::Base.logger
return unless logger
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << exception.backtrace.join("\n ")
- logger.fatal("#{message}\n\n")
+ logger.fatal do
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ "#{message}\n\n"
+ end
end
def response_body=(body)
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 3feb737277..acaa8227c9 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -68,14 +68,15 @@ module ActionController
# <tt>ActionController::RedirectBackError</tt>.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
+ raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
- self.location = _compute_redirect_to_location(options)
+ self.location = _compute_redirect_to_location(request, options)
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:
+ def _compute_redirect_to_location(request, options) #:nodoc:
case options
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
@@ -89,11 +90,13 @@ module ActionController
when :back
request.headers["Referer"] or raise RedirectBackError
when Proc
- _compute_redirect_to_location options.call
+ _compute_redirect_to_location request, options.call
else
url_for(options)
end.delete("\0\r\n")
end
+ module_function :_compute_redirect_to_location
+ public :_compute_redirect_to_location
private
def _extract_redirect_to_status(options, response_status)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 93e7d6954c..7bbff0450a 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -67,8 +67,8 @@ module ActionController
options[:html] = ERB::Util.html_escape(options[:html])
end
- if options.delete(:nothing) || _any_render_format_is_nil?(options)
- options[:body] = " "
+ if options.delete(:nothing)
+ options[:body] = nil
end
if options[:status]
@@ -86,10 +86,6 @@ module ActionController
end
end
- def _any_render_format_is_nil?(options)
- RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? }
- end
-
# Process controller specific options, as status, content-type and location.
def _process_options(options) #:nodoc:
status, content_type, location = options.values_at(:status, :content_type, :location)
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 1355fe87d0..0efa0fb259 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -77,7 +77,7 @@ module ActionController #:nodoc:
end
module ClassMethods
- # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
+ # Turn on request forgery protection. Bear in mind that GET and HEAD requests are not checked.
#
# class ApplicationController < ActionController::Base
# protect_from_forgery
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 07265be3fe..0f2fa5fb08 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -28,20 +28,19 @@ module ActionController
:port => request.optional_port,
:protocol => request.protocol,
:_recall => request.path_parameters
- }.merge(super).freeze
+ }.merge!(super).freeze
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'.freeze])
- @_url_options.dup.tap do |options|
- if original_script_name
- options[:original_script_name] = original_script_name
- else
- options[:script_name] = same_origin ? request.script_name.dup : script_name
- end
- options.freeze
+ options = @_url_options.dup
+ if original_script_name
+ options[:original_script_name] = original_script_name
+ else
+ options[:script_name] = same_origin ? request.script_name.dup : script_name
end
+ options.freeze
else
@_url_options
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 4c237b15a4..120cd55989 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -14,11 +14,13 @@ module ActionController
teardown :teardown_subscriptions
end
+ RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
+
def setup_subscriptions
- @_partials = Hash.new(0)
- @_templates = Hash.new(0)
- @_layouts = Hash.new(0)
- @_files = Hash.new(0)
+ RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
+ instance_variable_set("@_#{instance_variable}", Hash.new(0))
+ end
+
@_subscribers = []
@_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
@@ -58,12 +60,16 @@ module ActionController
end
def process(*args)
- @_partials = Hash.new(0)
- @_templates = Hash.new(0)
- @_layouts = Hash.new(0)
+ reset_template_assertion
super
end
+ def reset_template_assertion
+ RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
+ instance_variable_get("@_#{instance_variable}").clear
+ end
+ end
+
# Asserts that the request was rendered with the appropriate template file or partials.
#
# # assert that the "new" view template was rendered
@@ -453,7 +459,6 @@ module ActionController
end
def controller_class=(new_class)
- prepare_controller_class(new_class) if new_class
self._controller_class = new_class
end
@@ -470,11 +475,6 @@ module ActionController
Class === constant && constant < ActionController::Metal
end
end
-
- def prepare_controller_class(new_class)
- new_class.send :include, ActionController::TestCase::RaiseActionExceptions
- end
-
end
# Simulate a GET request with the given parameters.
@@ -697,12 +697,11 @@ module ActionController
unless @request.env["PATH_INFO"]
options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
options.update(
- :only_path => true,
:action => action,
:relative_url_root => nil,
:_recall => @request.path_parameters)
- url, query_string = @routes.url_for(options).split("?", 2)
+ url, query_string = @routes.path_for(options).split("?", 2)
@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
@request.env["PATH_INFO"] = url
@@ -716,34 +715,6 @@ module ActionController
end
end
- # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
- # (skipping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
- # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
- # than 0.0.0.0.
- #
- # The exception is stored in the exception accessor for further inspection.
- module RaiseActionExceptions
- def self.included(base) #:nodoc:
- unless base.method_defined?(:exception) && base.method_defined?(:exception=)
- base.class_eval do
- attr_accessor :exception
- protected :exception, :exception=
- end
- end
- end
-
- protected
- def rescue_action_without_handler(e)
- self.exception = e
-
- if request.remote_addr == "0.0.0.0"
- raise(e)
- else
- super(e)
- end
- end
- end
-
include Behavior
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 01f117be99..8c035c3c6c 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -23,7 +23,7 @@ module ActionDispatch
autoload :Session, 'action_dispatch/request/session'
autoload :Utils, 'action_dispatch/request/utils'
- LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
+ LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
@@ -225,7 +225,7 @@ module ActionDispatch
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
- # Returns the unique request id, which is based off either the X-Request-Id header that can
+ # Returns the unique request id, which is based on either the X-Request-Id header that can
# be generated by a firewall, load balancer, or web server or by the RequestId middleware
# (which sets the action_dispatch.request_id environment variable).
#
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 45bf751d09..540e11a4a0 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -27,7 +27,8 @@ module ActionDispatch
@tempfile = hash[:tempfile]
raise(ArgumentError, ':tempfile is required') unless @tempfile
- @original_filename = encode_filename(hash[:filename])
+ @original_filename = hash[:filename]
+ @original_filename &&= @original_filename.encode "UTF-8"
@content_type = hash[:type]
@headers = hash[:head]
end
@@ -66,13 +67,6 @@ module ActionDispatch
def eof?
@tempfile.eof?
end
-
- private
-
- def encode_filename(filename)
- # Encode the filename in the utf8 encoding, unless it is nil
- filename.force_encoding(Encoding::UTF_8).encode! if filename
- end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 6ba2820d09..6b8dcaf497 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -29,39 +29,48 @@ module ActionDispatch
end
def url_for(options)
- host = options[:host]
- unless 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'
+ if options[:only_path]
+ path_for options
+ else
+ full_url_for options
end
+ end
- path = options[:script_name].to_s.chomp("/")
- path << options[:path].to_s
+ def full_url_for(options)
+ host = options[:host]
+ protocol = options[:protocol]
+ port = options[:port]
- path = add_trailing_slash(path) if options[:trailing_slash]
+ unless host
+ 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 = if options[:only_path]
- path
- else
- protocol = options[:protocol]
- port = options[:port]
- build_host_url(host, port, protocol, options).concat path
- end
+ build_host_url(host, port, protocol, options, path_for(options))
+ end
- if options.key? :params
- params = options[:params].is_a?(Hash) ?
- options[:params] :
- { params: options[:params] }
+ def path_for(options)
+ path = options[:script_name].to_s.chomp("/")
+ path << options[:path] if options.key?(:path)
- params.reject! { |_,v| v.to_param.nil? }
- result << "?#{params.to_query}" unless params.empty?
- end
+ add_trailing_slash(path) if options[:trailing_slash]
+ add_params(path, options[:params]) if options.key?(:params)
+ add_anchor(path, options[:anchor]) if options.key?(:anchor)
- result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- result
+ path
end
private
+ def add_params(path, params)
+ params = { params: params } unless params.is_a?(Hash)
+ params.reject! { |_,v| v.to_param.nil? }
+ path << "?#{params.to_query}" unless params.empty?
+ end
+
+ def add_anchor(path, anchor)
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param.to_s)}"
+ end
+
def extract_domain_from(host, tld_length)
host.split('.').last(1 + tld_length).join('.')
end
@@ -79,19 +88,17 @@ module ActionDispatch
elsif !path.include?(".")
path.sub!(/[^\/]\z|\A\z/, '\&/')
end
-
- path
end
- def build_host_url(host, port, protocol, options)
+ def build_host_url(host, port, protocol, options, path)
if match = host.match(HOST_REGEXP)
- protocol ||= match[1] unless protocol == false
- host = match[2]
- port = match[3] unless options.key? :port
+ 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)
+ protocol = normalize_protocol protocol
+ host = normalize_host(host, options)
result = protocol.dup
@@ -104,7 +111,7 @@ module ActionDispatch
result << ":#{normalized_port}"
}
- result
+ result.concat path
end
def named_host?(host)
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
index 6d58323789..59b353b1b7 100644
--- a/actionpack/lib/action_dispatch/journey/formatter.rb
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -12,12 +12,12 @@ module ActionDispatch
@cache = nil
end
- def generate(name, options, recall = {}, parameterize = nil)
- constraints = recall.merge(options)
+ def generate(name, options, path_parameters, parameterize = nil)
+ constraints = path_parameters.merge(options)
missing_keys = []
match_route(name, constraints) do |route|
- parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
+ parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
# Skip this route unless a name has been provided or it is a
# standard Rails route since we can't determine whether an options
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index d129ba7e16..9012297400 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -86,7 +86,7 @@ racc_token_table = {
racc_nt_base = 10
-racc_use_result_var = true
+racc_use_result_var = false
Racc_arg = [
racc_action_table,
@@ -133,14 +133,12 @@ Racc_debug_parser = false
# reduce 0 omitted
-def _reduce_1(val, _values, result)
- result = Cat.new(val.first, val.last)
- result
+def _reduce_1(val, _values)
+ Cat.new(val.first, val.last)
end
-def _reduce_2(val, _values, result)
- result = val.first
- result
+def _reduce_2(val, _values)
+ val.first
end
# reduce 3 omitted
@@ -151,24 +149,20 @@ end
# reduce 6 omitted
-def _reduce_7(val, _values, result)
- result = Group.new(val[1])
- result
+def _reduce_7(val, _values)
+ Group.new(val[1])
end
-def _reduce_8(val, _values, result)
- result = Or.new([val.first, val.last])
- result
+def _reduce_8(val, _values)
+ Or.new([val.first, val.last])
end
-def _reduce_9(val, _values, result)
- result = Or.new([val.first, val.last])
- result
+def _reduce_9(val, _values)
+ Or.new([val.first, val.last])
end
-def _reduce_10(val, _values, result)
- result = Star.new(Symbol.new(val.last))
- result
+def _reduce_10(val, _values)
+ Star.new(Symbol.new(val.last))
end
# reduce 11 omitted
@@ -179,27 +173,23 @@ end
# reduce 14 omitted
-def _reduce_15(val, _values, result)
- result = Slash.new('/')
- result
+def _reduce_15(val, _values)
+ Slash.new('/')
end
-def _reduce_16(val, _values, result)
- result = Symbol.new(val.first)
- result
+def _reduce_16(val, _values)
+ Symbol.new(val.first)
end
-def _reduce_17(val, _values, result)
- result = Literal.new(val.first)
- result
+def _reduce_17(val, _values)
+ Literal.new(val.first)
end
-def _reduce_18(val, _values, result)
- result = Dot.new(val.first)
- result
+def _reduce_18(val, _values)
+ Dot.new(val.first)
end
-def _reduce_none(val, _values, result)
+def _reduce_none(val, _values)
val[0]
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
index 0ead222551..d3f7c4d765 100644
--- a/actionpack/lib/action_dispatch/journey/parser.y
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -1,11 +1,11 @@
class ActionDispatch::Journey::Parser
-
+ options no_result_var
token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
rule
expressions
- : expression expressions { result = Cat.new(val.first, val.last) }
- | expression { result = val.first }
+ : expression expressions { Cat.new(val.first, val.last) }
+ | expression { val.first }
| or
;
expression
@@ -14,14 +14,14 @@ rule
| star
;
group
- : LPAREN expressions RPAREN { result = Group.new(val[1]) }
+ : LPAREN expressions RPAREN { Group.new(val[1]) }
;
or
- : expression OR expression { result = Or.new([val.first, val.last]) }
- | expression OR or { result = Or.new([val.first, val.last]) }
+ : expression OR expression { Or.new([val.first, val.last]) }
+ | expression OR or { Or.new([val.first, val.last]) }
;
star
- : STAR { result = Star.new(Symbol.new(val.last)) }
+ : STAR { Star.new(Symbol.new(val.last)) }
;
terminal
: symbol
@@ -30,16 +30,16 @@ rule
| dot
;
slash
- : SLASH { result = Slash.new('/') }
+ : SLASH { Slash.new('/') }
;
symbol
- : SYMBOL { result = Symbol.new(val.first) }
+ : SYMBOL { Symbol.new(val.first) }
;
literal
- : LITERAL { result = Literal.new(val.first) }
+ : LITERAL { Literal.new(val.first) }
;
dot
- : DOT { result = Dot.new(val.first) }
+ : DOT { Dot.new(val.first) }
;
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index e069840b8e..ac9e5effe2 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -468,7 +468,7 @@ module ActionDispatch
options = { :value => @verifier.generate(serialize(name, options)) }
end
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@parent_jar[name] = options
end
@@ -526,7 +526,7 @@ module ActionDispatch
options[:value] = @encryptor.encrypt_and_sign(serialize(name, options[:value]))
- raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
+ raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
@parent_jar[name] = options
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 0ca1a87645..274f6f2f22 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -38,9 +38,7 @@ module ActionDispatch
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
request: request,
exception: wrapper.exception,
- application_trace: wrapper.application_trace,
- framework_trace: wrapper.framework_trace,
- full_trace: wrapper.full_trace,
+ traces: traces_from_wrapper(wrapper),
routes_inspector: routes_inspector(exception),
source_extract: wrapper.source_extract,
line_number: wrapper.line_number,
@@ -95,5 +93,36 @@ module ActionDispatch
ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
end
end
+
+ # Augment the exception traces by providing ids for all unique stack frame
+ def traces_from_wrapper(wrapper)
+ application_trace = wrapper.application_trace
+ framework_trace = wrapper.framework_trace
+ full_trace = wrapper.full_trace
+
+ if application_trace && framework_trace
+ id_counter = 0
+
+ application_trace = application_trace.map do |trace|
+ prev = id_counter
+ id_counter += 1
+ { id: prev, trace: trace }
+ end
+
+ framework_trace = framework_trace.map do |trace|
+ prev = id_counter
+ id_counter += 1
+ { id: prev, trace: trace }
+ end
+
+ full_trace = application_trace + framework_trace
+ end
+
+ {
+ "Application Trace" => application_trace,
+ "Framework Trace" => framework_trace,
+ "Full Trace" => full_trace
+ }
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 2326bb043a..b98b553c38 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -61,12 +61,15 @@ module ActionDispatch
end
def source_extract
- if application_trace && trace = application_trace.first
- file, line, _ = trace.split(":")
- @file = file
- @line_number = line.to_i
- source_fragment(@file, @line_number)
- end
+ exception.backtrace.map do |trace|
+ file, line = trace.split(":")
+ line_number = line.to_i
+ {
+ code: source_fragment(file, line_number),
+ file: file,
+ line_number: line_number
+ }
+ end if exception.backtrace
end
private
@@ -110,7 +113,7 @@ module ActionDispatch
def expand_backtrace
@exception.backtrace.unshift(
@exception.to_s.split("\n")
- ).flatten!
+ ).flatten!
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 5d1740d0d4..25658bac3d 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -5,7 +5,7 @@ module ActionDispatch
# Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
# ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
#
- # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
+ # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
# header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
#
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index 1db6194271..625050dc4b 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -16,9 +16,9 @@ module ActionDispatch
# Get a session from the cache.
def get_session(env, sid)
- sid ||= generate_sid
- session = @cache.read(cache_key(sid))
- session ||= {}
+ unless sid and session = @cache.read(cache_key(sid))
+ sid, session = generate_sid, {}
+ end
[sid, session]
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 1d4f0f89a6..f0779279c1 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -42,6 +42,7 @@ module ActionDispatch
wrapper = ExceptionWrapper.new(env, exception)
status = wrapper.status_code
env["action_dispatch.exception"] = wrapper.exception
+ env["action_dispatch.original_path"] = env["PATH_INFO"]
env["PATH_INFO"] = "/#{status}"
response = @exceptions_app.call(env)
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
index 38429cb78e..51660a619b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
@@ -1,25 +1,29 @@
<% if @source_extract %>
-<div class="source">
-<div class="info">
- Extracted source (around line <strong>#<%= @line_number %></strong>):
-</div>
-<div class="data">
- <table cellpadding="0" cellspacing="0" class="lines">
- <tr>
- <td>
- <pre class="line_numbers">
- <% @source_extract.keys.each do |line_number| %>
+ <% @source_extract.each_with_index do |extract_source, index| %>
+ <% if extract_source[:code] %>
+ <div class="source <%="hidden" if index != 0%>" id="frame-source-<%=index%>">
+ <div class="info">
+ Extracted source (around line <strong>#<%= extract_source[:line_number] %></strong>):
+ </div>
+ <div class="data">
+ <table cellpadding="0" cellspacing="0" class="lines">
+ <tr>
+ <td>
+ <pre class="line_numbers">
+ <% extract_source[:code].keys.each do |line_number| %>
<span><%= line_number -%></span>
- <% end %>
- </pre>
- </td>
+ <% end %>
+ </pre>
+ </td>
<td width="100%">
<pre>
-<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @line_number -%>"><%= source -%></div><% end -%>
+<% extract_source[:code].each do |line, source| -%><div class="line<%= " active" if line == extract_source[:line_number] -%>"><%= source -%></div><% end -%>
</pre>
</td>
- </tr>
- </table>
-</div>
-</div>
+ </tr>
+ </table>
+ </div>
+ </div>
+ <% end %>
+ <% end %>
<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
index b181909bff..f62caf51d7 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
@@ -1,9 +1,4 @@
-<%
- traces = { "Application Trace" => @application_trace,
- "Framework Trace" => @framework_trace,
- "Full Trace" => @full_trace }
- names = traces.keys
-%>
+<% names = @traces.keys %>
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
@@ -16,9 +11,42 @@
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
<% end %>
- <% traces.each do |name, trace| %>
+ <% @traces.each do |name, trace| %>
<div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
- <pre><code><%= trace.join "\n" %></code></pre>
+ <pre><code><% trace.each do |frame| %><a class="trace-frames" data-frame-id="<%= frame[:id] %>" href="#"><%= frame[:trace] %></a><br><% end %></code></pre>
</div>
<% end %>
+
+ <script type="text/javascript">
+ var traceFrames = document.getElementsByClassName('trace-frames');
+ var selectedFrame, currentSource = document.getElementById('frame-source-0');
+
+ // Add click listeners for all stack frames
+ for (var i = 0; i < traceFrames.length; i++) {
+ traceFrames[i].addEventListener('click', function(e) {
+ e.preventDefault();
+ var target = e.target;
+ var frame_id = target.dataset.frameId;
+
+ if (selectedFrame) {
+ selectedFrame.className = selectedFrame.className.replace("selected", "");
+ }
+
+ target.className += " selected";
+ selectedFrame = target;
+
+ // Change the extracted source code
+ changeSourceExtract(frame_id);
+ });
+
+ function changeSourceExtract(frame_id) {
+ var el = document.getElementById('frame-source-' + frame_id);
+ if (currentSource && el) {
+ currentSource.className += " hidden";
+ el.className = el.className.replace(" hidden", "");
+ currentSource = el;
+ }
+ }
+ }
+ </script>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
index d4af5c9b06..36b01bf952 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
@@ -1,15 +1,9 @@
-<%
- traces = { "Application Trace" => @application_trace,
- "Framework Trace" => @framework_trace,
- "Full Trace" => @full_trace }
-%>
-
Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>
-<% traces.each do |name, trace| %>
+<% @traces.each do |name, trace| %>
<% if trace.any? %>
<%= name %>
-<%= trace.join("\n") %>
+<%= trace.map(&:trace).join("\n") %>
<% end %>
<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index bc5d03dc10..e0509f56f4 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -116,9 +116,15 @@
background-color: #FFCCCC;
}
+ .hidden {
+ display: none;
+ }
+
a { color: #980905; }
a:visited { color: #666; }
+ a.trace-frames { color: #666; }
a:hover { color: #C52F24; }
+ a.trace-frames.selected { color: #C52F24 }
<%= yield :style %>
</style>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
index 027a0f5b3e..c1e8b6cae3 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb
@@ -1,4 +1,3 @@
-<% @source_extract = @exception.source_extract(0, :html) %>
<header>
<h1>
<%= @exception.original_exception.class.to_s %> in
@@ -12,29 +11,7 @@
</p>
<pre><code><%= h @exception.message %></code></pre>
- <div class="source">
- <div class="info">
- <p>Extracted source (around line <strong>#<%= @exception.line_number %></strong>):</p>
- </div>
- <div class="data">
- <table cellpadding="0" cellspacing="0" class="lines">
- <tr>
- <td>
- <pre class="line_numbers">
- <% @source_extract.keys.each do |line_number| %>
-<span><%= line_number -%></span>
- <% end %>
- </pre>
- </td>
-<td width="100%">
-<pre>
-<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @exception.line_number -%>"><%= source -%></div><% end -%>
-</pre>
-</td>
- </tr>
- </table>
-</div>
-</div>
+ <%= render template: "rescues/_source" %>
<p><%= @exception.sub_template_message %></p>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
index 5da21d9784..77bcd26726 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb
@@ -1,4 +1,3 @@
-<% @source_extract = @exception.source_extract(0, :html) %>
<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 235a840682..cd94f35e8f 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -13,9 +13,6 @@ module ActionDispatch
module Routing
class Mapper
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
- SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
- :controller, :action, :path_names, :constraints,
- :shallow, :blocks, :defaults, :options]
class Constraints < Endpoint #:nodoc:
attr_reader :app, :constraints
@@ -66,7 +63,7 @@ module ActionDispatch
attr_reader :requirements, :conditions, :defaults
attr_reader :to, :default_controller, :default_action, :as, :anchor
- def self.build(scope, path, options)
+ def self.build(scope, set, path, options)
options = scope[:options].merge(options) if scope[:options]
options.delete :only
@@ -77,12 +74,13 @@ module ActionDispatch
defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
- new scope, path, defaults, options
+ new scope, set, path, defaults, options
end
- def initialize(scope, path, defaults, options)
+ def initialize(scope, set, path, defaults, options)
@requirements, @conditions = {}, {}
@defaults = defaults
+ @set = set
@to = options.delete :to
@default_controller = options.delete(:controller) || scope[:controller]
@@ -249,9 +247,9 @@ module ActionDispatch
Constraints.new(to, blocks, false)
else
if blocks.any?
- Constraints.new(dispatcher, blocks, true)
+ Constraints.new(dispatcher(defaults), blocks, true)
else
- dispatcher
+ dispatcher(defaults)
end
end
end
@@ -348,8 +346,8 @@ module ActionDispatch
parser.parse path
end
- def dispatcher
- Routing::RouteSet::Dispatcher.new(defaults)
+ def dispatcher(defaults)
+ @set.dispatcher defaults
end
end
@@ -576,13 +574,21 @@ module ActionDispatch
raise "A rack application must be specified" unless path
- options[:as] ||= app_name(app)
+ rails_app = rails_app? app
+
+ if rails_app
+ options[:as] ||= app.railtie_name
+ else
+ # non rails apps can't have an :as
+ options[:as] = nil
+ end
+
target_as = name_for_action(options[:as], path)
options[:via] ||= :all
match(path, options.merge(:to => app, :anchor => false, :format => false))
- define_generate_prefix(app, target_as)
+ define_generate_prefix(app, target_as) if rails_app
self
end
@@ -603,31 +609,24 @@ module ActionDispatch
end
private
- def app_name(app)
- return unless app.respond_to?(:routes)
-
- if app.respond_to?(:railtie_name)
- app.railtie_name
- else
- class_name = app.class.is_a?(Class) ? app.name : app.class.name
- ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
- end
+ def rails_app?(app)
+ app.is_a?(Class) && app < Rails::Railtie
end
def define_generate_prefix(app, name)
- return unless app.respond_to?(:routes) && app.routes.respond_to?(:define_mounted_helper)
-
- _route = @set.named_routes.routes[name.to_sym]
+ _route = @set.named_routes.get name
_routes = @set
app.routes.define_mounted_helper(name)
app.routes.extend Module.new {
- def mounted?; true; end
+ def optimize_routes_generation?; false; 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)
+ if options.key? :script_name
+ super(options)
+ else
+ 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
}
@@ -771,7 +770,7 @@ module ActionDispatch
# end
def scope(*args)
options = args.extract_options!.dup
- recover = {}
+ scope = {}
options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
@@ -791,7 +790,7 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
- SCOPE_OPTIONS.each do |option|
+ @scope.options.each do |option|
if option == :blocks
value = block
elsif option == :options
@@ -801,15 +800,15 @@ module ActionDispatch
end
if value
- recover[option] = @scope[option]
- @scope[option] = send("merge_#{option}_scope", @scope[option], value)
+ scope[option] = send("merge_#{option}_scope", @scope[option], value)
end
end
+ @scope = @scope.new scope
yield
self
ensure
- @scope.merge!(recover)
+ @scope = @scope.parent
end
# Scopes routes to a specific controller
@@ -1545,13 +1544,13 @@ module ActionDispatch
action = nil
end
- if !options.fetch(:as, true)
+ if !options.fetch(:as, true) # if it's set to nil or false
options.delete(:as)
else
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.build(@scope, URI.parser.escape(path), options)
+ mapping = Mapping.build(@scope, @set, URI.parser.escape(path), options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
@@ -1645,27 +1644,26 @@ module ActionDispatch
def with_exclusive_scope
begin
- old_name_prefix, old_path = @scope[:as], @scope[:path]
- @scope[:as], @scope[:path] = nil, nil
+ @scope = @scope.new(:as => nil, :path => nil)
with_scope_level(:exclusive) do
yield
end
ensure
- @scope[:as], @scope[:path] = old_name_prefix, old_path
+ @scope = @scope.parent
end
end
def with_scope_level(kind)
- old, @scope[:scope_level] = @scope[:scope_level], kind
+ @scope = @scope.new(:scope_level => kind)
yield
ensure
- @scope[:scope_level] = old
+ @scope = @scope.parent
end
def resource_scope(kind, resource) #:nodoc:
resource.shallow = @scope[:shallow]
- old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
+ @scope = @scope.new(:scope_level_resource => resource)
@nesting.push(resource)
with_scope_level(kind) do
@@ -1673,7 +1671,7 @@ module ActionDispatch
end
ensure
@nesting.pop
- @scope[:scope_level_resource] = old_resource
+ @scope = @scope.parent
end
def nested_options #:nodoc:
@@ -1706,12 +1704,13 @@ module ActionDispatch
end
def shallow_scope(path, options = {}) #:nodoc:
- old_name_prefix, old_path = @scope[:as], @scope[:path]
- @scope[:as], @scope[:path] = @scope[:shallow_prefix], @scope[:shallow_path]
+ scope = { :as => @scope[:shallow_prefix],
+ :path => @scope[:shallow_path] }
+ @scope = @scope.new scope
scope(path, options) { yield }
ensure
- @scope[:as], @scope[:path] = old_name_prefix, old_path
+ @scope = @scope.parent
end
def path_for_action(action, path) #:nodoc:
@@ -1768,7 +1767,7 @@ module ActionDispatch
# and return nil in case it isn't. Otherwise, we pass the invalid name
# forward so the underlying router engine treats it and raises an exception.
if as.nil?
- candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
+ candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
else
candidate
end
@@ -1893,9 +1892,38 @@ module ActionDispatch
end
end
+ class Scope # :nodoc:
+ OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
+ :controller, :action, :path_names, :constraints,
+ :shallow, :blocks, :defaults, :options]
+
+ attr_reader :parent
+
+ def initialize(hash, parent = {})
+ @hash = hash
+ @parent = parent
+ end
+
+ def options
+ OPTIONS
+ end
+
+ def new(hash)
+ self.class.new hash, self
+ end
+
+ def [](key)
+ @hash.fetch(key) { @parent[key] }
+ end
+
+ def []=(k,v)
+ @hash[k] = v
+ end
+ end
+
def initialize(set) #:nodoc:
@set = set
- @scope = { :path_names => @set.resources_path_names }
+ @scope = Scope.new({ :path_names => @set.resources_path_names })
@concerns = {}
@nesting = []
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 69535faabd..5b3651aaee 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -86,36 +86,64 @@ module ActionDispatch
# named routes.
class NamedRouteCollection #:nodoc:
include Enumerable
- attr_reader :routes, :helpers, :module
+ attr_reader :routes, :url_helpers_module
def initialize
@routes = {}
- @helpers = []
- @module = Module.new
+ @path_helpers = Set.new
+ @url_helpers = Set.new
+ @url_helpers_module = Module.new
+ @path_helpers_module = Module.new
+ end
+
+ def route_defined?(name)
+ key = name.to_sym
+ @path_helpers.include?(key) || @url_helpers.include?(key)
end
def helper_names
- @helpers.map(&:to_s)
+ @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
end
def clear!
- @helpers.each do |helper|
- @module.remove_possible_method helper
+ @path_helpers.each do |helper|
+ @path_helpers_module.send :undef_method, helper
+ end
+
+ @url_helpers.each do |helper|
+ @url_helpers_module.send :undef_method, helper
end
@routes.clear
- @helpers.clear
+ @path_helpers.clear
+ @url_helpers.clear
end
def add(name, route)
- routes[name.to_sym] = route
- define_named_route_methods(name, route)
+ key = name.to_sym
+ path_name = :"#{name}_path"
+ url_name = :"#{name}_url"
+
+ if routes.key? key
+ @path_helpers_module.send :undef_method, path_name
+ @url_helpers_module.send :undef_method, url_name
+ end
+ routes[key] = route
+ define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
+ define_url_helper @url_helpers_module, route, url_name, route.defaults, name, FULL
+
+ @path_helpers << path_name
+ @url_helpers << url_name
end
def get(name)
routes[name.to_sym]
end
+ def key?(name)
+ routes.key? name.to_sym
+ end
+
alias []= add
alias [] get
alias clear clear!
@@ -133,12 +161,31 @@ module ActionDispatch
routes.length
end
+ def path_helpers_module(warn = false)
+ if warn
+ mod = @path_helpers_module
+ helpers = @path_helpers
+ Module.new do
+ include mod
+
+ helpers.each do |meth|
+ define_method(meth) do |*args, &block|
+ ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead")
+ super(*args, &block)
+ end
+ end
+ end
+ else
+ @path_helpers_module
+ end
+ end
+
class UrlHelper # :nodoc:
- def self.create(route, options)
+ def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
- OptimizedUrlHelper.new(route, options)
+ OptimizedUrlHelper.new(route, options, route_name, url_strategy)
else
- new route, options
+ new route, options, route_name, url_strategy
end
end
@@ -146,20 +193,22 @@ module ActionDispatch
!route.glob? && route.path.requirements.empty?
end
+ attr_reader :url_strategy, :route_name
+
class OptimizedUrlHelper < UrlHelper # :nodoc:
attr_reader :arg_size
- def initialize(route, options)
+ def initialize(route, options, route_name, url_strategy)
super
@required_parts = @route.required_parts
@arg_size = @required_parts.size
end
- def call(t, args)
- if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
+ def call(t, args, inner_options)
+ if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
options = t.url_options.merge @options
options[:path] = optimized_helper(args)
- ActionDispatch::Http::URL.url_for(options)
+ url_strategy.call options
else
super
end
@@ -201,21 +250,27 @@ module ActionDispatch
end
end
- def initialize(route, options)
+ def initialize(route, options, route_name, url_strategy)
@options = options
@segment_keys = route.segment_keys.uniq
@route = route
+ @url_strategy = url_strategy
+ @route_name = route_name
end
- def call(t, args)
+ def call(t, args, inner_options)
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)
+ hash = handle_positional_args(controller_options,
+ inner_options || {},
+ args,
+ options,
+ @segment_keys)
+
+ t._routes.url_for(hash, route_name, url_strategy)
end
- def handle_positional_args(controller_options, args, result, path_params)
- inner_options = args.extract_options!
+ def handle_positional_args(controller_options, inner_options, args, result, path_params)
if args.size > 0
if args.size < path_params.size - 1 # take format into account
@@ -245,27 +300,25 @@ module ActionDispatch
#
# foo_url(bar, baz, bang, sort_by: 'baz')
#
- def define_url_helper(route, name, options)
- helper = UrlHelper.create(route, options.dup)
-
- @module.remove_possible_method name
- @module.module_eval do
+ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
+ helper = UrlHelper.create(route, opts, route_key, url_strategy)
+ mod.module_eval do
define_method(name) do |*args|
- helper.call self, args
+ options = nil
+ options = args.pop if args.last.is_a? Hash
+ helper.call self, args, options
end
end
-
- helpers << name
- end
-
- def define_named_route_methods(name, route)
- define_url_helper route, :"#{name}_path",
- route.defaults.merge(:use_route => name, :only_path => true)
- define_url_helper route, :"#{name}_url",
- route.defaults.merge(:use_route => name, :only_path => false)
end
end
+ # :stopdoc:
+ # strategy for building urls to send to the client
+ PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
+ FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) }
+ UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
+ # :startdoc:
+
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class
@@ -278,7 +331,7 @@ module ActionDispatch
def initialize(request_class = ActionDispatch::Request)
self.named_routes = NamedRouteCollection.new
- self.resources_path_names = self.class.default_resources_path_names.dup
+ self.resources_path_names = self.class.default_resources_path_names
self.default_url_options = {}
self.request_class = request_class
@@ -319,6 +372,7 @@ module ActionDispatch
mapper.instance_exec(&block)
end
end
+ private :eval_block
def finalize!
return if @finalized
@@ -334,6 +388,10 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
+ def dispatcher(defaults)
+ Routing::RouteSet::Dispatcher.new(defaults)
+ end
+
module MountedHelpers #:nodoc:
extend ActiveSupport::Concern
include UrlFor
@@ -364,42 +422,51 @@ module ActionDispatch
RUBY
end
- def url_helpers
- @url_helpers ||= begin
- routes = self
-
- Module.new do
- extend ActiveSupport::Concern
- include UrlFor
-
- # Define url_for in the singleton level so one can do:
- # Rails.application.routes.url_helpers.url_for(args)
- @_routes = routes
- class << self
- delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
- attr_reader :_routes
- def url_options; {}; end
- end
+ def url_helpers(include_path_helpers = true)
+ routes = self
- # Make named_routes available in the module singleton
- # as well, so one can do:
- # Rails.application.routes.url_helpers.posts_path
- extend routes.named_routes.module
+ Module.new do
+ extend ActiveSupport::Concern
+ include UrlFor
+
+ # Define url_for in the singleton level so one can do:
+ # Rails.application.routes.url_helpers.url_for(args)
+ @_routes = routes
+ class << self
+ delegate :url_for, :optimize_routes_generation?, to: '@_routes'
+ attr_reader :_routes
+ def url_options; {}; end
+ end
- # Any class that includes this module will get all
- # named routes...
- include routes.named_routes.module
+ url_helpers = routes.named_routes.url_helpers_module
- # plus a singleton class method called _routes ...
- included do
- singleton_class.send(:redefine_method, :_routes) { routes }
- end
+ # Make named_routes available in the module singleton
+ # as well, so one can do:
+ # Rails.application.routes.url_helpers.posts_path
+ extend url_helpers
- # And an instance method _routes. Note that
- # UrlFor (included in this module) add extra
- # conveniences for working with @_routes.
- define_method(:_routes) { @_routes || routes }
+ # Any class that includes this module will get all
+ # named routes...
+ include url_helpers
+
+ if include_path_helpers
+ path_helpers = routes.named_routes.path_helpers_module
+ else
+ path_helpers = routes.named_routes.path_helpers_module(true)
end
+
+ include path_helpers
+ extend path_helpers
+
+ # plus a singleton class method called _routes ...
+ included do
+ singleton_class.send(:redefine_method, :_routes) { routes }
+ end
+
+ # And an instance method _routes. Note that
+ # UrlFor (included in this module) add extra
+ # conveniences for working with @_routes.
+ define_method(:_routes) { @_routes || routes }
end
end
@@ -491,8 +558,8 @@ module ActionDispatch
attr_reader :options, :recall, :set, :named_route
- def initialize(options, recall, set)
- @named_route = options.delete(:use_route)
+ def initialize(named_route, options, recall, set)
+ @named_route = named_route
@options = options.dup
@recall = recall.dup
@set = set
@@ -608,32 +675,34 @@ module ActionDispatch
end
def generate_extras(options, recall={})
- path, params = generate(options, recall)
+ route_key = options.delete :use_route
+ path, params = generate(route_key, options, recall)
return path, params.keys
end
- def generate(options, recall = {})
- Generator.new(options, recall, self).generate
+ def generate(route_key, options, recall = {})
+ Generator.new(route_key, options, recall, self).generate
end
+ private :generate
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
:trailing_slash, :anchor, :params, :only_path, :script_name,
:original_script_name]
- def mounted?
- false
- end
-
def optimize_routes_generation?
- !mounted? && default_url_options.empty?
+ default_url_options.empty?
end
def find_script_name(options)
- options.delete :script_name
+ options.delete(:script_name) { '' }
+ end
+
+ def path_for(options, route_name = nil) # :nodoc:
+ url_for(options, route_name, PATH)
end
# The +options+ argument must be a hash whose keys are *symbols*.
- def url_for(options)
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN)
options = default_url_options.merge options
user = password = nil
@@ -648,14 +717,14 @@ module ActionDispatch
original_script_name = options.delete(:original_script_name)
script_name = find_script_name options
- if script_name && original_script_name
+ if original_script_name
script_name = original_script_name + script_name
end
path_options = options.dup
RESERVED_OPTIONS.each { |ro| path_options.delete ro }
- path, params = generate(path_options, recall)
+ path, params = generate(route_name, path_options, recall)
if options.key? :params
params.merge! options[:params]
@@ -667,7 +736,7 @@ module ActionDispatch
options[:user] = user
options[:password] = password
- ActionDispatch::Http::URL.url_for(options)
+ url_strategy.call options
end
def call(env)
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index e624fe3c4a..eb554ec383 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -152,7 +152,9 @@ module ActionDispatch
when nil
_routes.url_for(url_options.symbolize_keys)
when Hash
- _routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
+ route_name = options.delete :use_route
+ _routes.url_for(options.symbolize_keys.reverse_merge!(url_options),
+ route_name)
when String
options
when Symbol
@@ -169,8 +171,7 @@ module ActionDispatch
protected
def optimize_routes_generation?
- return @_optimized_routes if defined?(@_optimized_routes)
- @_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
+ _routes.optimize_routes_generation? && default_url_options.empty?
end
def _with_routes(routes)
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 0adc6c84ff..13a72220b3 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -73,13 +73,8 @@ module ActionDispatch
if Regexp === fragment
fragment
else
- 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)
+ handle = @controller || ActionController::Redirecting
+ handle._compute_redirect_to_location(@request, fragment)
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index f1f998d932..2cf38a9c2d 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -165,7 +165,7 @@ module ActionDispatch
# ROUTES TODO: These assertions should really work in an integration context
def method_missing(selector, *args, &block)
- if defined?(@controller) && @controller && @routes && @routes.named_routes.helpers.include?(selector)
+ if defined?(@controller) && @controller && @routes && @routes.named_routes.route_defined?(selector)
@controller.send(selector, *args, &block)
else
super
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index e326776bbc..2d1c3ac5c7 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -3,6 +3,7 @@ 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:
@@ -200,7 +201,7 @@ module ActionDispatch
@url_options ||= default_url_options.dup.tap do |url_options|
url_options.reverse_merge!(controller.url_options) if controller
- if @app.respond_to?(:routes) && @app.routes.respond_to?(:default_url_options)
+ if @app.respond_to?(:routes)
url_options.reverse_merge!(@app.routes.default_url_options)
end
@@ -329,6 +330,7 @@ module ActionDispatch
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
+ reset_template_assertion
# reset the html_document variable, but only for new get/post calls
@html_document = nil unless method == 'cookies' || method == 'assigns'
integration_session.__send__(method, *args).tap do
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 6584d20840..376add06ca 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -15,6 +15,12 @@ silence_warnings do
Encoding.default_external = "UTF-8"
end
+require 'drb'
+require 'drb/unix'
+require 'tempfile'
+
+PROCESS_COUNT = (ENV['N'] || 4).to_i
+
require 'active_support/testing/autorun'
require 'abstract_controller'
require 'action_controller'
@@ -105,6 +111,9 @@ end
module ActiveSupport
class TestCase
include ActionDispatch::DrawOnce
+ if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0
+ parallelize_me!
+ end
end
end
@@ -305,22 +314,95 @@ end
module ActionDispatch
module RoutingVerbs
- def get(uri_or_host, path = nil)
+ def send_request(uri_or_host, method, path)
host = uri_or_host.host unless path
path ||= uri_or_host.path
params = {'PATH_INFO' => path,
- 'REQUEST_METHOD' => 'GET',
+ 'REQUEST_METHOD' => method,
'HTTP_HOST' => host}
- routes.call(params)[2].join
+ routes.call(params)
+ end
+
+ def request_path_params(path, options = {})
+ method = options[:method] || 'GET'
+ resp = send_request URI('http://localhost' + path), method.to_s.upcase, nil
+ status = resp.first
+ if status == 404
+ raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ end
+ controller.request.path_parameters
+ end
+
+ def get(uri_or_host, path = nil)
+ send_request(uri_or_host, 'GET', path)[2].join
+ end
+
+ def post(uri_or_host, path = nil)
+ send_request(uri_or_host, 'POST', path)[2].join
+ end
+
+ def put(uri_or_host, path = nil)
+ send_request(uri_or_host, 'PUT', path)[2].join
+ end
+
+ def delete(uri_or_host, path = nil)
+ send_request(uri_or_host, 'DELETE', path)[2].join
+ end
+
+ def patch(uri_or_host, path = nil)
+ send_request(uri_or_host, 'PATCH', path)[2].join
end
end
end
module RoutingTestHelpers
- def url_for(set, options, recall = {})
- set.url_for options.merge(:only_path => true, :_recall => recall)
+ def url_for(set, options)
+ route_name = options.delete :use_route
+ set.url_for options.merge(:only_path => true), route_name
+ end
+
+ def make_set(strict = true)
+ tc = self
+ TestSet.new ->(c) { tc.controller = c }, strict
+ end
+
+ class TestSet < ActionDispatch::Routing::RouteSet
+ attr_reader :strict
+
+ def initialize(block, strict = false)
+ @block = block
+ @strict = strict
+ super()
+ end
+
+ class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher
+ def initialize(defaults, set, block)
+ super(defaults)
+ @block = block
+ @set = set
+ end
+
+ def controller(params, default_controller=true)
+ super(params, @set.strict)
+ end
+
+ def controller_reference(controller_param)
+ block = @block
+ set = @set
+ super if @set.strict
+ Class.new(ActionController::Base) {
+ include set.url_helpers
+ define_method(:process) { |name| block.call(self) }
+ def to_a; [200, {}, []]; end
+ }
+ end
+ end
+
+ def dispatcher defaults
+ TestSet::Dispatcher.new defaults, self, @block
+ end
end
end
@@ -359,3 +441,64 @@ end
def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+class ForkingExecutor
+ class Server
+ include DRb::DRbUndumped
+
+ def initialize
+ @queue = Queue.new
+ end
+
+ def record reporter, result
+ reporter.record result
+ end
+
+ def << o
+ o[2] = DRbObject.new(o[2]) if o
+ @queue << o
+ end
+ def pop; @queue.pop; end
+ end
+
+ def initialize size
+ @size = size
+ @queue = Server.new
+ file = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('tests', 'fd')
+ @url = "drbunix://#{file}"
+ @pool = nil
+ DRb.start_service @url, @queue
+ end
+
+ def << work; @queue << work; end
+
+ def shutdown
+ pool = @size.times.map {
+ fork {
+ DRb.stop_service
+ queue = DRbObject.new_with_uri @url
+ while job = queue.pop
+ klass = job[0]
+ method = job[1]
+ reporter = job[2]
+ result = Minitest.run_one_method klass, method
+ queue.record reporter, result
+ end
+ }
+ }
+ @size.times { @queue << nil }
+ pool.each { |pid| Process.waitpid pid }
+ end
+end
+
+if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0
+ # Use N processes (N defaults to 4)
+ Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT)
+end
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 2cd32ef85a..99b53e6fd2 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'controller/fake_controllers'
+require 'rails/engine'
class SessionTest < ActiveSupport::TestCase
StubApp = lambda { |env|
@@ -594,7 +595,7 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
@routes ||= ActionDispatch::Routing::RouteSet.new
end
- class MountedApp
+ class MountedApp < Rails::Engine
def self.routes
@routes ||= ActionDispatch::Routing::RouteSet.new
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 18037b3d2f..49be7caf38 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -85,7 +85,7 @@ class ACLogSubscriberTest < ActionController::TestCase
@old_logger = ActionController::Base.logger
- @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__))
+ @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('tmp', 'cache')
@controller.cache_store = :file_store, @cache_path
ActionController::LogSubscriber.attach_to :action_controller
end
diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb
index fad848349a..f4a3db8b41 100644
--- a/actionpack/test/controller/new_base/render_body_test.rb
+++ b/actionpack/test/controller/new_base/render_body_test.rb
@@ -111,17 +111,17 @@ module RenderBody
assert_status 404
end
- test "rendering body with nil returns an empty body padded for Safari" do
+ test "rendering body with nil returns an empty body" do
get "/render_body/with_layout/with_nil"
- assert_body " "
+ assert_body ""
assert_status 200
end
- test "Rendering body with nil and custom status code returns an empty body padded for Safari and the status" do
+ test "Rendering body with nil and custom status code returns an empty body and the status" do
get "/render_body/with_layout/with_nil_and_status"
- assert_body " "
+ assert_body ""
assert_status 403
end
diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb
index bfe0271df7..fe11501eeb 100644
--- a/actionpack/test/controller/new_base/render_html_test.rb
+++ b/actionpack/test/controller/new_base/render_html_test.rb
@@ -114,17 +114,17 @@ module RenderHtml
assert_status 404
end
- test "rendering text with nil returns an empty body padded for Safari" do
+ test "rendering text with nil returns an empty body" do
get "/render_html/with_layout/with_nil"
- assert_body " "
+ assert_body ""
assert_status 200
end
- test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ test "Rendering text with nil and custom status code returns an empty body and the status" do
get "/render_html/with_layout/with_nil_and_status"
- assert_body " "
+ assert_body ""
assert_status 403
end
diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb
index dba2e9f13e..0e36d36b50 100644
--- a/actionpack/test/controller/new_base/render_plain_test.rb
+++ b/actionpack/test/controller/new_base/render_plain_test.rb
@@ -106,17 +106,17 @@ module RenderPlain
assert_status 404
end
- test "rendering text with nil returns an empty body padded for Safari" do
+ test "rendering text with nil returns an empty body" do
get "/render_plain/with_layout/with_nil"
- assert_body " "
+ assert_body ""
assert_status 200
end
- test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ test "Rendering text with nil and custom status code returns an empty body and the status" do
get "/render_plain/with_layout/with_nil_and_status"
- assert_body " "
+ assert_body ""
assert_status 403
end
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index b7a9cf92f2..e87811776a 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -9,7 +9,7 @@ module RenderTemplate
"locals.html.erb" => "The secret is <%= secret %>",
"xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
"with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
- "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in a html template",
+ "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in an html template",
"with_implicit_raw.text.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in a text template",
"test/with_json.html.erb" => "<%= render :template => 'test/with_json', :formats => [:json] %>",
"test/with_json.json.erb" => "<%= render :template => 'test/final', :formats => [:json] %>",
@@ -114,7 +114,7 @@ module RenderTemplate
get :with_implicit_raw
- assert_body "Hello <strong>this is also raw</strong> in a html template"
+ assert_body "Hello <strong>this is also raw</strong> in an html template"
assert_status 200
get :with_implicit_raw, format: 'text'
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index abb81d7e71..10bad57cd6 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -106,17 +106,17 @@ module RenderText
assert_status 404
end
- test "rendering text with nil returns an empty body padded for Safari" do
+ test "rendering text with nil returns an empty body" do
get "/render_text/with_layout/with_nil"
- assert_body " "
+ assert_body ""
assert_status 200
end
- test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ test "Rendering text with nil and custom status code returns an empty body and the status" do
get "/render_text/with_layout/with_nil_and_status"
- assert_body " "
+ assert_body ""
assert_status 403
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 4331333b98..103ca9c776 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -90,6 +90,10 @@ class RedirectController < ActionController::Base
redirect_to nil
end
+ def redirect_to_params
+ redirect_to ActionController::Parameters.new(status: 200, protocol: 'javascript', f: '%0Aeval(name)')
+ end
+
def redirect_to_with_block
redirect_to proc { "http://www.rubyonrails.org/" }
end
@@ -281,6 +285,12 @@ class RedirectTest < ActionController::TestCase
end
end
+ def test_redirect_to_params
+ assert_raise(ActionController::ActionControllerError) do
+ get :redirect_to_params
+ end
+ end
+
def test_redirect_to_with_block
get :redirect_to_with_block
assert_response :redirect
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 721dad4dd9..c18914cc8e 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -77,10 +77,11 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
include ActionDispatch::RoutingVerbs
attr_reader :rs
+ attr_accessor :controller
alias :routes :rs
def setup
- @rs = ::ActionDispatch::Routing::RouteSet.new
+ @rs = make_set
@response = nil
end
@@ -317,11 +318,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal '/admin/user/show/10', url_for(rs, { :controller => 'admin/user', :action => 'show', :id => 10 })
- assert_equal '/admin/user/show', url_for(rs, { :action => 'show' }, { :controller => 'admin/user', :action => 'list', :id => '10' })
- assert_equal '/admin/user/list/10', url_for(rs, {}, { :controller => 'admin/user', :action => 'list', :id => '10' })
+ get URI('http://test.host/admin/user/list/10')
- assert_equal '/admin/stuff', url_for(rs, { :controller => 'stuff' }, { :controller => 'admin/user', :action => 'list', :id => '10' })
- assert_equal '/stuff', url_for(rs, { :controller => '/stuff' }, { :controller => 'admin/user', :action => 'list', :id => '10' })
+ assert_equal({ :controller => 'admin/user', :action => 'list', :id => '10' },
+ controller.request.path_parameters)
+
+ assert_equal '/admin/user/show', controller.url_for({ :action => 'show', :only_path => true })
+ assert_equal '/admin/user/list/10', controller.url_for({:only_path => true})
+
+ assert_equal '/admin/stuff', controller.url_for({ :controller => 'stuff', :only_path => true })
+ assert_equal '/stuff', controller.url_for({ :controller => '/stuff', :only_path => true })
end
def test_ignores_leading_slash
@@ -518,9 +524,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_changing_controller
rs.draw { get ':controller/:action/:id' }
+ get URI('http://test.host/admin/user/index/10')
+
assert_equal '/admin/stuff/show/10',
- url_for(rs, {:controller => 'stuff', :action => 'show', :id => 10},
- {:controller => 'admin/user', :action => 'index'})
+ controller.url_for({:controller => 'stuff', :action => 'show', :id => 10, :only_path => true})
end
def test_paths_escaped
@@ -579,8 +586,12 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get '*path' => 'content#show_file'
end
+ get URI('http://test.host/pages/boo')
+ assert_equal({:controller=>"content", :action=>"show_file", :path=>"pages/boo"},
+ controller.request.path_parameters)
+
assert_equal '/pages/boo',
- url_for(rs, {}, { :controller => 'content', :action => 'show_file', :path => %w(pages boo) })
+ controller.url_for(:only_path => true)
end
def test_backwards
@@ -589,7 +600,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
get ':controller(/:action(/:id))'
end
- assert_equal '/page/20', url_for(rs, { :id => 20 }, { :controller => 'pages', :action => 'show' })
+ get URI('http://test.host/pages/show')
+ assert_equal '/page/20', controller.url_for({ :id => 20, :only_path => true })
assert_equal '/page/20', url_for(rs, { :controller => 'pages', :id => 20, :action => 'show' })
assert_equal '/pages/boo', url_for(rs, { :controller => 'pages', :action => 'boo' })
end
@@ -630,7 +642,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
def test_action_expiry
rs.draw { get ':controller(/:action(/:id))' }
- assert_equal '/content', url_for(rs, { :controller => 'content' }, { :controller => 'content', :action => 'show' })
+ get URI('http://test.host/content/show')
+ assert_equal '/content', controller.url_for(:controller => 'content', :only_path => true)
end
def test_requirement_should_prevent_optional_id
@@ -673,14 +686,18 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal '/pages/2005/6/12',
url_for(rs, { :controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12 })
+ get URI('http://test.host/pages/2005/6/12')
+ assert_equal({ :controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12' },
+ controller.request.path_parameters)
+
assert_equal '/pages/2005/6/4',
- url_for(rs, { :day => 4 }, { :controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12' })
+ controller.url_for({ :day => 4, :only_path => true })
assert_equal '/pages/2005/6',
- url_for(rs, { :day => nil }, { :controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12' })
+ controller.url_for({ :day => nil, :only_path => true })
assert_equal '/pages/2005',
- url_for(rs, { :day => nil, :month => nil }, { :controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12' })
+ controller.url_for({ :day => nil, :month => nil, :only_path => true })
end
def test_root_url_generation_with_controller_and_action
@@ -838,9 +855,15 @@ end
class RouteSetTest < ActiveSupport::TestCase
include RoutingTestHelpers
+ include ActionDispatch::RoutingVerbs
- def set
- @set ||= ROUTING::RouteSet.new
+ attr_reader :set
+ alias :routes :set
+ attr_accessor :controller
+
+ def setup
+ super
+ @set = make_set
end
def request
@@ -941,7 +964,8 @@ class RouteSetTest < ActiveSupport::TestCase
get '/admin/users' => 'admin/users#index', :as => "users"
end
- MockController.build(set.url_helpers).new
+ get URI('http://test.host/people')
+ controller
end
def test_named_route_url_method
@@ -1038,12 +1062,12 @@ class RouteSetTest < ActiveSupport::TestCase
get '/:controller(/:action(/:id))'
end
- assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
- assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index'))
- assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list'))
+ assert_equal({:controller => 'pages', :action => 'index'}, request_path_params('/pages'))
+ assert_equal({:controller => 'pages', :action => 'index'}, request_path_params('/pages/index'))
+ assert_equal({:controller => 'pages', :action => 'list'}, request_path_params('/pages/list'))
- assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10'))
- assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, request_path_params('/pages/show/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, request_path_params('/page/10'))
end
def test_route_constraints_on_request_object_with_anchors_are_valid
@@ -1095,9 +1119,7 @@ class RouteSetTest < ActiveSupport::TestCase
get "/people" => "missing#index"
end
- assert_raise(ActionController::RoutingError) {
- set.recognize_path("/people", :method => :get)
- }
+ assert_raises(ActionController::RoutingError) { request_path_params '/people' }
end
def test_recognize_with_encoded_id_and_regex
@@ -1105,8 +1127,8 @@ class RouteSetTest < ActiveSupport::TestCase
get 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/
end
- assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
- assert_equal({:controller => 'pages', :action => 'show', :id => 'hello+world'}, set.recognize_path('/page/hello+world'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, request_path_params('/page/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => 'hello+world'}, request_path_params('/page/hello+world'))
end
def test_recognize_with_http_methods
@@ -1119,40 +1141,40 @@ class RouteSetTest < ActiveSupport::TestCase
delete "/people/:id" => "people#destroy"
end
- params = set.recognize_path("/people", :method => :get)
+ params = request_path_params("/people", :method => :get)
assert_equal("index", params[:action])
- params = set.recognize_path("/people", :method => :post)
+ params = request_path_params("/people", :method => :post)
assert_equal("create", params[:action])
- params = set.recognize_path("/people/5", :method => :put)
+ params = request_path_params("/people/5", :method => :put)
assert_equal("update", params[:action])
- params = set.recognize_path("/people/5", :method => :patch)
+ params = request_path_params("/people/5", :method => :patch)
assert_equal("update", params[:action])
assert_raise(ActionController::UnknownHttpMethod) {
- set.recognize_path("/people", :method => :bacon)
+ request_path_params("/people", :method => :bacon)
}
- params = set.recognize_path("/people/5", :method => :get)
+ params = request_path_params("/people/5", :method => :get)
assert_equal("show", params[:action])
assert_equal("5", params[:id])
- params = set.recognize_path("/people/5", :method => :put)
+ params = request_path_params("/people/5", :method => :put)
assert_equal("update", params[:action])
assert_equal("5", params[:id])
- params = set.recognize_path("/people/5", :method => :patch)
+ params = request_path_params("/people/5", :method => :patch)
assert_equal("update", params[:action])
assert_equal("5", params[:id])
- params = set.recognize_path("/people/5", :method => :delete)
+ params = request_path_params("/people/5", :method => :delete)
assert_equal("destroy", params[:action])
assert_equal("5", params[:id])
assert_raise(ActionController::RoutingError) {
- set.recognize_path("/people/5", :method => :post)
+ request_path_params("/people/5", :method => :post)
}
end
@@ -1162,11 +1184,11 @@ class RouteSetTest < ActiveSupport::TestCase
root :to => "people#index"
end
- params = set.recognize_path("/people", :method => :get)
+ params = request_path_params("/people", :method => :get)
assert_equal("people", params[:controller])
assert_equal("index", params[:action])
- params = set.recognize_path("/", :method => :get)
+ params = request_path_params("/", :method => :get)
assert_equal("people", params[:controller])
assert_equal("index", params[:action])
end
@@ -1177,7 +1199,7 @@ class RouteSetTest < ActiveSupport::TestCase
:year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/
end
- params = set.recognize_path("/articles/2005/11/05/a-very-interesting-article", :method => :get)
+ params = request_path_params("/articles/2005/11/05/a-very-interesting-article", :method => :get)
assert_equal("permalink", params[:action])
assert_equal("2005", params[:year])
assert_equal("11", params[:month])
@@ -1191,7 +1213,7 @@ class RouteSetTest < ActiveSupport::TestCase
get '/profile' => 'profile#index'
end
- set.recognize_path("/profile") rescue nil
+ request_path_params("/profile") rescue nil
assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded"
end
@@ -1204,17 +1226,17 @@ class RouteSetTest < ActiveSupport::TestCase
get "people/:id(.:format)" => "people#show"
end
- params = set.recognize_path("/people/5", :method => :get)
+ params = request_path_params("/people/5", :method => :get)
assert_equal("show", params[:action])
assert_equal("5", params[:id])
- params = set.recognize_path("/people/5", :method => :put)
+ params = request_path_params("/people/5", :method => :put)
assert_equal("update", params[:action])
- params = set.recognize_path("/people/5", :method => :patch)
+ params = request_path_params("/people/5", :method => :patch)
assert_equal("update", params[:action])
- params = set.recognize_path("/people/5.png", :method => :get)
+ params = request_path_params("/people/5.png", :method => :get)
assert_equal("show", params[:action])
assert_equal("5", params[:id])
assert_equal("png", params[:format])
@@ -1233,7 +1255,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_root_map
set.draw { root :to => 'people#index' }
- params = set.recognize_path("", :method => :get)
+ params = request_path_params("", :method => :get)
assert_equal("people", params[:controller])
assert_equal("index", params[:action])
end
@@ -1247,7 +1269,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
- params = set.recognize_path("/api/inventory", :method => :get)
+ params = request_path_params("/api/inventory", :method => :get)
assert_equal("api/products", params[:controller])
assert_equal("inventory", params[:action])
end
@@ -1259,7 +1281,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
- params = set.recognize_path("/api", :method => :get)
+ params = request_path_params("/api", :method => :get)
assert_equal("api/products", params[:controller])
assert_equal("index", params[:action])
end
@@ -1271,7 +1293,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
- params = set.recognize_path("/prefix/inventory", :method => :get)
+ params = request_path_params("/prefix/inventory", :method => :get)
assert_equal("api/products", params[:controller])
assert_equal("inventory", params[:action])
end
@@ -1283,38 +1305,36 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
- params = set.recognize_path("/inventory", :method => :get)
+ params = request_path_params("/inventory", :method => :get)
assert_equal("api/products", params[:controller])
assert_equal("inventory", params[:action])
end
- def test_generate_changes_controller_module
- set.draw { get ':controller/:action/:id' }
- current = { :controller => "bling/bloop", :action => "bap", :id => 9 }
-
- assert_equal "/foo/bar/baz/7",
- url_for(set, { :controller => "foo/bar", :action => "baz", :id => 7 }, current)
- end
-
def test_id_is_sticky_when_it_ought_to_be
+ @set = make_set false
+
set.draw do
get ':controller/:id/:action'
end
- url = url_for(set, { :action => "destroy" }, { :controller => "people", :action => "show", :id => "7" })
- assert_equal "/people/7/destroy", url
+ get URI('http://test.host/people/7/show')
+
+ assert_equal "/people/7/destroy", controller.url_for(:action => 'destroy', :only_path => true)
end
def test_use_static_path_when_possible
+ @set = make_set false
+
set.draw do
get 'about' => "welcome#about"
get ':controller/:action/:id'
end
- url = url_for(set, { :controller => "welcome", :action => "about" },
- { :controller => "welcome", :action => "get", :id => "7" })
+ get URI('http://test.host/welcom/get/7')
- assert_equal "/about", url
+ assert_equal "/about", controller.url_for(:controller => 'welcome',
+ :action => 'about',
+ :only_path => true)
end
def test_generate
@@ -1349,38 +1369,51 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_named_routes_are_never_relative_to_modules
+ @set = make_set false
+
set.draw do
get "/connection/manage(/:action)" => 'connection/manage#index'
get "/connection/connection" => "connection/connection#index"
get '/connection' => 'connection#index', :as => 'family_connection'
end
- url = url_for(set, { :controller => "connection" }, { :controller => 'connection/manage' })
+ assert_equal({ :controller => 'connection/manage',
+ :action => 'index', }, request_path_params('/connection/manage'))
+
+ url = controller.url_for({ :controller => "connection", :only_path => true })
assert_equal "/connection/connection", url
- url = url_for(set, { :use_route => :family_connection, :controller => "connection" }, { :controller => 'connection/manage' })
+ url = controller.url_for({ :use_route => :family_connection,
+ :controller => "connection", :only_path => true })
assert_equal "/connection", url
end
def test_action_left_off_when_id_is_recalled
+ @set = make_set false
+
set.draw do
get ':controller(/:action(/:id))'
end
- assert_equal '/books', url_for(set,
- {:controller => 'books', :action => 'index'},
- {:controller => 'books', :action => 'show', :id => '10'}
- )
+
+ get URI('http://test.host/books/show/10')
+
+ assert_equal '/books', controller.url_for(:controller => 'books',
+ :only_path => true,
+ :action => 'index')
end
def test_query_params_will_be_shown_when_recalled
+ @set = make_set false
+
set.draw do
get 'show_weblog/:parameter' => 'weblog#show'
get ':controller(/:action(/:id))'
end
- assert_equal '/weblog/edit?parameter=1', url_for(set,
- {:action => 'edit', :parameter => 1},
- {:controller => 'weblog', :action => 'show', :parameter => 1}
- )
+
+ get URI('http://test.host/weblog/show/1')
+
+ assert_equal '/weblog/edit?parameter=1', controller.url_for(
+ {:action => 'edit', :parameter => 1, :only_path => true})
end
def test_format_is_not_inherit
@@ -1388,22 +1421,30 @@ class RouteSetTest < ActiveSupport::TestCase
get '/posts(.:format)' => 'posts#index'
end
- assert_equal '/posts', url_for(set,
- {:controller => 'posts'},
- {:controller => 'posts', :action => 'index', :format => 'xml'}
- )
+ get URI('http://test.host/posts.xml')
+ assert_equal({:controller => 'posts', :action => 'index', :format => 'xml'},
+ controller.request.path_parameters)
+
+ assert_equal '/posts', controller.url_for(
+ {:controller => 'posts', :only_path => true})
- assert_equal '/posts.xml', url_for(set,
- {:controller => 'posts', :format => 'xml'},
- {:controller => 'posts', :action => 'index', :format => 'xml'}
- )
+ assert_equal '/posts.xml', controller.url_for(
+ {:controller => 'posts', :format => 'xml', :only_path => true})
end
def test_expiry_determination_should_consider_values_with_to_param
+ @set = make_set false
+
set.draw { get 'projects/:project_id/:controller/:action' }
- assert_equal '/projects/1/weblog/show', url_for(set,
- { :action => 'show', :project_id => 1 },
- { :controller => 'weblog', :action => 'show', :project_id => '1' })
+
+ get URI('http://test.host/projects/1/weblog/show')
+
+ assert_equal(
+ { :controller => 'weblog', :action => 'show', :project_id => '1' },
+ controller.request.path_parameters)
+
+ assert_equal '/projects/1/weblog/show',
+ controller.url_for({ :action => 'show', :project_id => 1, :only_path => true })
end
def test_named_route_in_nested_resource
@@ -1606,7 +1647,6 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_slashes_are_implied
- @set = nil
set.draw { get("/:controller(/:action(/:id))") }
assert_equal '/content', url_for(set, { :controller => 'content', :action => 'index' })
@@ -1704,7 +1744,43 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 })
end
+ include ActionDispatch::RoutingVerbs
+
+ class TestSet < ROUTING::RouteSet
+ def initialize(block)
+ @block = block
+ super()
+ end
+
+ class Dispatcher < ROUTING::RouteSet::Dispatcher
+ def initialize(defaults, set, block)
+ super(defaults)
+ @block = block
+ @set = set
+ end
+
+ def controller_reference(controller_param)
+ block = @block
+ set = @set
+ Class.new(ActionController::Base) {
+ include set.url_helpers
+ define_method(:process) { |name| block.call(self) }
+ def to_a; [200, {}, []]; end
+ }
+ end
+ end
+
+ def dispatcher defaults
+ TestSet::Dispatcher.new defaults, self, @block
+ end
+ end
+
+ alias :routes :set
+
def test_generate_with_optional_params_recalls_last_request
+ controller = nil
+ @set = TestSet.new ->(c) { controller = c }
+
set.draw do
get "blog/", :controller => "blog", :action => "index"
@@ -1719,23 +1795,29 @@ class RouteSetTest < ActiveSupport::TestCase
get "*anything", :controller => "blog", :action => "unknown_request"
end
- assert_equal({:controller => "blog", :action => "index"}, set.recognize_path("/blog"))
- assert_equal({:controller => "blog", :action => "show", :id => "123"}, set.recognize_path("/blog/show/123"))
- assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :day => nil, :month => nil }, set.recognize_path("/blog/2004"))
- assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => nil }, set.recognize_path("/blog/2004/12"))
- assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => "25"}, set.recognize_path("/blog/2004/12/25"))
- assert_equal({:controller => "articles", :action => "edit", :id => "123"}, set.recognize_path("/blog/articles/edit/123"))
- assert_equal({:controller => "articles", :action => "show_stats"}, set.recognize_path("/blog/articles/show_stats"))
- assert_equal({:controller => "blog", :action => "unknown_request", :anything => "blog/wibble"}, set.recognize_path("/blog/wibble"))
- assert_equal({:controller => "blog", :action => "unknown_request", :anything => "junk"}, set.recognize_path("/junk"))
+ recognize_path = ->(path) {
+ get(URI("http://example.org" + path))
+ controller.request.path_parameters
+ }
+
+ assert_equal({:controller => "blog", :action => "index"}, recognize_path.("/blog"))
+ assert_equal({:controller => "blog", :action => "show", :id => "123"}, recognize_path.("/blog/show/123"))
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :day => nil, :month => nil }, recognize_path.("/blog/2004"))
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => nil }, recognize_path.("/blog/2004/12"))
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => "25"}, recognize_path.("/blog/2004/12/25"))
+ assert_equal({:controller => "articles", :action => "edit", :id => "123"}, recognize_path.("/blog/articles/edit/123"))
+ assert_equal({:controller => "articles", :action => "show_stats"}, recognize_path.("/blog/articles/show_stats"))
+ assert_equal({:controller => "blog", :action => "unknown_request", :anything => "blog/wibble"}, recognize_path.("/blog/wibble"))
+ assert_equal({:controller => "blog", :action => "unknown_request", :anything => "junk"}, recognize_path.("/junk"))
+
+ get URI('http://example.org/blog/2006/07/28')
- last_request = set.recognize_path("/blog/2006/07/28").freeze
- assert_equal({:controller => "blog", :action => "show_date", :year => "2006", :month => "07", :day => "28"}, last_request)
- assert_equal("/blog/2006/07/25", url_for(set, { :day => 25 }, last_request))
- assert_equal("/blog/2005", url_for(set, { :year => 2005 }, last_request))
- assert_equal("/blog/show/123", url_for(set, { :action => "show" , :id => 123 }, last_request))
- assert_equal("/blog/2006", url_for(set, { :year => 2006 }, last_request))
- assert_equal("/blog/2006", url_for(set, { :year => 2006, :month => nil }, last_request))
+ assert_equal({:controller => "blog", :action => "show_date", :year => "2006", :month => "07", :day => "28"}, controller.request.path_parameters)
+ assert_equal("/blog/2006/07/25", controller.url_for({ :day => 25, :only_path => true }))
+ assert_equal("/blog/2005", controller.url_for({ :year => 2005, :only_path => true }))
+ assert_equal("/blog/show/123", controller.url_for({ :action => "show" , :id => 123, :only_path => true }))
+ assert_equal("/blog/2006", controller.url_for({ :year => 2006, :only_path => true }))
+ assert_equal("/blog/2006", controller.url_for({ :year => 2006, :month => nil, :only_path => true }))
end
private
@@ -1808,6 +1890,9 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
root :to => "news#index"
}
+ attr_reader :routes
+ attr_reader :controller
+
def setup
@routes = ActionDispatch::Routing::RouteSet.new
@routes.draw(&Mapping)
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index ff23b22040..f7eba1ef43 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -32,7 +32,7 @@ module ShowExceptions
test 'show diagnostics from a local ip if show_detailed_exceptions? is set to request.local?' do
@app = ShowExceptionsController.action(:boom)
- ['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
+ ['127.0.0.1', '127.0.0.127', '127.12.1.1', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
self.remote_addr = ip_address
get '/'
assert_match(/boom/, body)
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 8bf850a200..1280e0d9a3 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -221,7 +221,7 @@ XML
assert_equal 200, @response.status
end
- def test_head_params_as_sting
+ def test_head_params_as_string
assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
end
@@ -534,6 +534,7 @@ XML
def test_header_properly_reset_after_remote_http_request
xhr :get, :test_params
assert_nil @request.env['HTTP_X_REQUESTED_WITH']
+ assert_nil @request.env['HTTP_ACCEPT']
end
def test_header_properly_reset_after_get_request
@@ -542,10 +543,10 @@ XML
assert_nil @request.instance_variable_get("@request_method")
end
- def test_params_reset_after_post_request
+ def test_params_reset_between_post_requests
post :no_op, :foo => "bar"
assert_equal "bar", @request.params[:foo]
- @request.recycle!
+
post :no_op
assert @request.params[:foo].blank?
end
@@ -558,10 +559,10 @@ XML
assert_equal "baz", @request.filtered_parameters[:foo]
end
- def test_path_params_reset_after_request
+ def test_path_params_reset_between_request
get :test_params, :id => "foo"
assert_equal "foo", @request.path_parameters[:id]
- @request.recycle!
+
get :test_params
assert_nil @request.path_parameters[:id]
end
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index 6c2311e7a5..24a09222b1 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -6,6 +6,7 @@ require 'active_support/core_ext/object/with_options'
module ActionPack
class URLForIntegrationTest < ActiveSupport::TestCase
include RoutingTestHelpers
+ include ActionDispatch::RoutingVerbs
Model = Struct.new(:to_param)
@@ -61,8 +62,11 @@ module ActionPack
root :to => "news#index"
}
+ attr_reader :routes
+ attr_accessor :controller
+
def setup
- @routes = ActionDispatch::Routing::RouteSet.new
+ @routes = make_set false
@routes.draw(&Mapping)
end
@@ -70,9 +74,9 @@ module ActionPack
['/admin/users',[ { :use_route => 'admin_users' }]],
['/admin/users',[ { :controller => 'admin/users' }]],
['/admin/users',[ { :controller => 'admin/users', :action => 'index' }]],
- ['/admin/users',[ { :action => 'index' }, { :controller => 'admin/users' }]],
- ['/admin/users',[ { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts' }]],
- ['/people',[ { :controller => '/people', :action => 'index' }, { :controller => 'admin/accounts' }]],
+ ['/admin/users',[ { :action => 'index' }, { :controller => 'admin/users', :action => 'index' }, '/admin/users']],
+ ['/admin/users',[ { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts', :action => 'show', :id => '1' }, '/admin/accounts/show/1']],
+ ['/people',[ { :controller => '/people', :action => 'index' }, {:controller=>"admin/accounts", :action=>"foo", :id=>"bar"}, '/admin/accounts/foo/bar']],
['/admin/posts',[ { :controller => 'admin/posts' }]],
['/admin/posts/new',[ { :controller => 'admin/posts', :action => 'new' }]],
@@ -86,11 +90,11 @@ module ActionPack
['/archive?year=january',[ { :controller => 'archive', :action => 'index', :year => 'january' }]],
['/people',[ { :controller => 'people', :action => 'index' }]],
- ['/people',[ { :action => 'index' }, { :controller => 'people' }]],
- ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
- ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
- ['/people',[ {}, { :controller => 'people', :action => 'index' }]],
- ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'index' }, '/people']],
+ ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people',[ {}, { :controller => 'people', :action => 'index' }, '/people']],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
['/people/new',[ { :use_route => 'new_person' }]],
['/people/new',[ { :controller => 'people', :action => 'new' }]],
['/people/1',[ { :use_route => 'person', :id => '1' }]],
@@ -98,11 +102,11 @@ module ActionPack
['/people/1.xml',[ { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' }]],
['/people/1',[ { :controller => 'people', :action => 'show', :id => 1 }]],
['/people/1',[ { :controller => 'people', :action => 'show', :id => Model.new('1') }]],
- ['/people/1',[ { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' }]],
- ['/people/1',[ { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' }]],
- ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }]],
- ['/people/1',[ {}, { :controller => 'people', :action => 'show', :id => '1' }]],
- ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' }]],
+ ['/people/1',[ { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' }, '/people']],
+ ['/people/1',[ { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people/1',[ {}, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' }, '/people/index/1']],
['/people/1/edit',[ { :controller => 'people', :action => 'edit', :id => '1' }]],
['/people/1/edit.xml',[ { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' }]],
['/people/1/edit',[ { :use_route => 'edit_person', :id => '1' }]],
@@ -118,16 +122,15 @@ module ActionPack
['/project',[ { :controller => 'project', :action => 'index' }]],
['/projects/1',[ { :controller => 'project', :action => 'index', :project_id => '1' }]],
- ['/projects/1',[ { :controller => 'project', :action => 'index'}, {:project_id => '1' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index'}, {:project_id => '1', :controller => 'project', :action => 'index' }, '/projects/1']],
['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' }]],
- ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index' }, { :project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index' }, { :controller => 'project', :action => 'index', :project_id => '1' }, '/projects/1']],
['/clients',[ { :controller => 'projects', :action => 'index' }]],
['/clients?project_id=1',[ { :controller => 'projects', :action => 'index', :project_id => '1' }]],
- ['/clients',[ { :controller => 'projects', :action => 'index' }, { :project_id => '1' }]],
- ['/clients',[ { :action => 'index' }, { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+ ['/clients',[ { :controller => 'projects', :action => 'index' }, { :project_id => '1', :controller => 'project', :action => 'index' }, '/projects/1']],
- ['/comment/20',[ { :id => 20 }, { :controller => 'comments', :action => 'show' }]],
+ ['/comment/20',[ { :id => 20 }, { :controller => 'comments', :action => 'show' }, '/comments/show']],
['/comment/20',[ { :controller => 'comments', :id => 20, :action => 'show' }]],
['/comments/boo',[ { :controller => 'comments', :action => 'boo' }]],
@@ -144,24 +147,21 @@ module ActionPack
['/notes',[ { :page_id => nil, :controller => 'notes' }]],
['/notes',[ { :controller => 'notes' }]],
['/notes/print',[ { :controller => 'notes', :action => 'print' }]],
- ['/notes/print',[ {}, { :controller => 'notes', :action => 'print' }]],
-
- ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
- ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1', :foo => 'bar' }]],
- ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1' }]],
- ['/notes/index/1',[ { :action => 'index' }, { :controller => 'notes', :id => '1' }]],
- ['/notes/index/1',[ {}, { :controller => 'notes', :id => '1' }]],
- ['/notes/show/1',[ {}, { :controller => 'notes', :action => 'show', :id => '1' }]],
- ['/notes/index/1',[ { :controller => 'notes', :id => '1' }, { :foo => 'bar' }]],
- ['/posts',[ { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
- ['/notes/list',[ { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/notes/print',[ {}, { :controller => 'notes', :action => 'print' }, '/notes/print']],
+
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :action => 'index', :id => '1' }, '/notes/index/1']],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']],
+ ['/notes/index/1',[ { :action => 'index' }, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']],
+ ['/notes/index/1',[ {}, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']],
+ ['/notes/show/1',[ {}, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']],
+ ['/notes/list',[ { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']],
['/posts/ping',[ { :controller => 'posts', :action => 'ping' }]],
['/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1' }]],
['/posts',[ { :controller => 'posts' }]],
['/posts',[ { :controller => 'posts', :action => 'index' }]],
- ['/posts',[ { :controller => 'posts' }, { :controller => 'posts', :action => 'index' }]],
- ['/posts/create',[ { :action => 'create' }, { :controller => 'posts' }]],
+ ['/posts/create',[ { :action => 'create' }, {:day=>nil, :month=>nil, :controller=>"posts", :action=>"show_date"}, '/blog']],
['/posts?foo=bar',[ { :controller => 'posts', :foo => 'bar' }]],
['/posts?foo%5B%5D=bar&foo%5B%5D=baz', [{ :controller => 'posts', :foo => ['bar', 'baz'] }]],
['/posts?page=2', [{ :controller => 'posts', :page => 2 }]],
@@ -169,9 +169,20 @@ module ActionPack
['/news.rss', [{ :controller => 'news', :action => 'index', :format => 'rss' }]],
].each_with_index do |(url, params), i|
- define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
- assert_equal url, url_for(@routes, *params), params.inspect
- end
+ if params.length > 1
+ hash, path_params, route = *params
+ hash[:only_path] = true
+
+ define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
+ get URI('http://test.host' + route.to_s)
+ assert_equal path_params, controller.request.path_parameters
+ assert_equal url, controller.url_for(hash), params.inspect
+ end
+ else
+ define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
+ assert_equal url, url_for(@routes, params.first), params.inspect
+ end
+ end
end
end
end
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 7210c68e73..9f086af664 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -287,12 +287,12 @@ module AbstractController
# We need to create a new class in order to install the new named route.
kls = Class.new { include set.url_helpers }
controller = kls.new
- assert controller.respond_to?(:home_url)
+ assert_respond_to controller, :home_url
assert_equal '/brave/new/world',
- controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
+ controller.url_for(:controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
- assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true))
- assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama'))
+ assert_equal("/home/sweet/home/alabama", controller.home_path(:user => 'alabama', :host => 'unused', :only_path => true))
+ assert_equal("/home/sweet/home/alabama", controller.home_path('alabama'))
end
end
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 8660deb634..24526fb00e 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -10,6 +10,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
@closed = false
end
+ # We're obliged to implement this (even though it doesn't actually
+ # get called here) to properly comply with the Rack SPEC
def each
end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index d8d3209dac..3e554a9cf6 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module ActionDispatch
module Routing
class MapperTest < ActiveSupport::TestCase
- class FakeSet
+ class FakeSet < ActionDispatch::Routing::RouteSet
attr_reader :routes
alias :set :routes
@@ -38,7 +38,7 @@ module ActionDispatch
def test_mapping_requirements
options = { :controller => 'foo', :action => 'bar', :via => :get }
- m = Mapper::Mapping.build({}, '/store/:name(*rest)', options)
+ m = Mapper::Mapping.build({}, FakeSet.new, '/store/:name(*rest)', options)
_, _, requirements, _ = m.to_route
assert_equal(/.+?/, requirements[:rest])
end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index ff4b644c16..d5a4d8ee11 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -1,12 +1,18 @@
require 'abstract_unit'
+require 'rails/engine'
class TestRoutingMount < ActionDispatch::IntegrationTest
Router = ActionDispatch::Routing::RouteSet.new
- class FakeEngine
+ class AppWithRoutes < Rails::Engine
def self.routes
@routes ||= ActionDispatch::Routing::RouteSet.new
end
+ end
+
+ # Test for mounting apps that respond to routes, but aren't Rails-like apps.
+ class SinatraLikeApp
+ def self.routes; Object.new; end
def self.call(env)
[200, {"Content-Type" => "text/html"}, ["OK"]]
@@ -21,15 +27,15 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
mount SprocketsApp, :at => "/sprockets"
mount SprocketsApp => "/shorthand"
- mount FakeEngine, :at => "/fakeengine", :as => :fake
- mount FakeEngine, :at => "/getfake", :via => :get
+ mount SinatraLikeApp, :at => "/fakeengine", :as => :fake
+ mount SinatraLikeApp, :at => "/getfake", :via => :get
scope "/its_a" do
mount SprocketsApp, :at => "/sprocket"
end
resources :users do
- mount FakeEngine, :at => "/fakeengine", :as => :fake_mounted_at_resource
+ mount AppWithRoutes, :at => "/fakeengine", :as => :fake_mounted_at_resource
end
mount SprocketsApp, :at => "/", :via => :get
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index cd31e8e326..f90d5499d7 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'rack/test'
+require 'rails/engine'
module TestGenerationPrefix
class Post
@@ -23,73 +24,48 @@ module TestGenerationPrefix
class WithMountedEngine < ActionDispatch::IntegrationTest
include Rack::Test::Methods
- class BlogEngine
- def self.routes
- @routes ||= begin
- routes = ActionDispatch::Routing::RouteSet.new
- routes.draw do
- get "/posts/:id", :to => "inside_engine_generating#show", :as => :post
- get "/posts", :to => "inside_engine_generating#index", :as => :posts
- get "/url_to_application", :to => "inside_engine_generating#url_to_application"
- get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine"
- get "/conflicting_url", :to => "inside_engine_generating#conflicting"
- get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
-
- get "/relative_path_root", :to => redirect("")
- get "/relative_path_redirect", :to => redirect("foo")
- get "/relative_option_root", :to => redirect(:path => "")
- get "/relative_option_redirect", :to => redirect(:path => "foo")
- get "/relative_custom_root", :to => redirect { |params, request| "" }
- get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
-
- get "/absolute_path_root", :to => redirect("/")
- get "/absolute_path_redirect", :to => redirect("/foo")
- get "/absolute_option_root", :to => redirect(:path => "/")
- get "/absolute_option_redirect", :to => redirect(:path => "/foo")
- get "/absolute_custom_root", :to => redirect { |params, request| "/" }
- get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
- end
-
- routes
- end
- end
-
- def self.call(env)
- env['action_dispatch.routes'] = routes
- routes.call(env)
+ class BlogEngine < Rails::Engine
+ routes.draw do
+ get "/posts/:id", :to => "inside_engine_generating#show", :as => :post
+ get "/posts", :to => "inside_engine_generating#index", :as => :posts
+ get "/url_to_application", :to => "inside_engine_generating#url_to_application"
+ get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine"
+ get "/conflicting_url", :to => "inside_engine_generating#conflicting"
+ get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
+
+ get "/relative_path_root", :to => redirect("")
+ get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_option_root", :to => redirect(:path => "")
+ get "/relative_option_redirect", :to => redirect(:path => "foo")
+ get "/relative_custom_root", :to => redirect { |params, request| "" }
+ get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
+
+ get "/absolute_path_root", :to => redirect("/")
+ get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_option_root", :to => redirect(:path => "/")
+ get "/absolute_option_redirect", :to => redirect(:path => "/foo")
+ get "/absolute_custom_root", :to => redirect { |params, request| "/" }
+ get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
end
end
- class RailsApplication
- def self.routes
- @routes ||= begin
- routes = ActionDispatch::Routing::RouteSet.new
- routes.draw do
- scope "/:omg", :omg => "awesome" do
- mount BlogEngine => "/blog", :as => "blog_engine"
- end
- get "/posts/:id", :to => "outside_engine_generating#post", :as => :post
- get "/generate", :to => "outside_engine_generating#index"
- get "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
- get "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
- get "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
- get "/conflicting_url", :to => "outside_engine_generating#conflicting"
- get "/ivar_usage", :to => "outside_engine_generating#ivar_usage"
- root :to => "outside_engine_generating#index"
- end
-
- routes
+ class RailsApplication < Rails::Engine
+ routes.draw do
+ scope "/:omg", :omg => "awesome" do
+ mount BlogEngine => "/blog", :as => "blog_engine"
end
- end
-
- def self.call(env)
- env['action_dispatch.routes'] = routes
- routes.call(env)
+ get "/posts/:id", :to => "outside_engine_generating#post", :as => :post
+ get "/generate", :to => "outside_engine_generating#index"
+ get "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
+ get "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
+ get "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
+ get "/conflicting_url", :to => "outside_engine_generating#conflicting"
+ get "/ivar_usage", :to => "outside_engine_generating#ivar_usage"
+ root :to => "outside_engine_generating#index"
end
end
# force draw
- RailsApplication.routes
RailsApplication.routes.define_mounted_helper(:main_app)
class ::InsideEngineGeneratingController < ActionController::Base
@@ -161,19 +137,15 @@ module TestGenerationPrefix
end
def app
- RailsApplication
+ RailsApplication.instance
end
- def engine_object
- @engine_object ||= EngineObject.new
- end
-
- def app_object
- @app_object ||= AppObject.new
- end
+ attr_reader :engine_object, :app_object
def setup
RailsApplication.routes.default_url_options = {}
+ @engine_object = EngineObject.new
+ @app_object = AppObject.new
end
include BlogEngine.routes.mounted_helpers
@@ -395,27 +367,12 @@ module TestGenerationPrefix
end
end
- class RailsApplication
- def self.routes
- @routes ||= begin
- routes = ActionDispatch::Routing::RouteSet.new
- routes.draw do
- mount BlogEngine => "/"
- end
-
- routes
- end
- end
-
- def self.call(env)
- env['action_dispatch.routes'] = routes
- routes.call(env)
+ class RailsApplication < Rails::Engine
+ routes.draw do
+ mount BlogEngine => "/"
end
end
- # force draw
- RailsApplication.routes
-
class ::PostsController < ActionController::Base
include BlogEngine.routes.url_helpers
include RailsApplication.routes.mounted_helpers
@@ -426,7 +383,7 @@ module TestGenerationPrefix
end
def app
- RailsApplication
+ RailsApplication.instance
end
test "generating path inside engine" do
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 1ef2b062dd..6737609567 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -528,6 +528,13 @@ class RequestCGI < BaseRequestTest
end
end
+class LocalhostTest < BaseRequestTest
+ test "IPs that match localhost" do
+ request = stub_request("REMOTE_IP" => "127.1.1.1", "REMOTE_ADDR" => "127.1.1.1")
+ assert request.local?
+ end
+end
+
class RequestCookie < BaseRequestTest
test "cookie syntax resilience" do
request = stub_request("HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes")
@@ -632,12 +639,14 @@ class RequestProtocol < BaseRequestTest
end
class RequestMethod < BaseRequestTest
- test "request methods" do
- [:post, :get, :patch, :put, :delete].each do |method|
- request = stub_request('REQUEST_METHOD' => method.to_s.upcase)
+ test "method returns environment's request method when it has not been
+ overriden by middleware".squish do
- assert_equal method.to_s.upcase, request.method
- assert_equal method, request.method_symbol
+ ActionDispatch::Request::HTTP_METHODS.each do |method|
+ request = stub_request('REQUEST_METHOD' => method)
+
+ assert_equal method, request.method
+ assert_equal method.underscore.to_sym, request.method_symbol
end
end
@@ -647,28 +656,18 @@ class RequestMethod < BaseRequestTest
end
end
- test "allow method hacking on post" do
- %w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
- request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
-
- assert_equal(method == "HEAD" ? "GET" : method, request.method)
- end
+ test "method returns original value of environment request method on POST" do
+ request = stub_request('rack.methodoverride.original_method' => 'POST')
+ assert_equal 'POST', request.method
end
- test "invalid method hacking on post raises exception" do
+ test "method raises exception on invalid HTTP method" do
assert_raise(ActionController::UnknownHttpMethod) do
- stub_request('REQUEST_METHOD' => '_RANDOM_METHOD').request_method
+ stub_request('rack.methodoverride.original_method' => '_RANDOM_METHOD').method
end
- end
- test "restrict method hacking" do
- [:get, :patch, :put, :delete].each do |method|
- request = stub_request(
- 'action_dispatch.request.request_parameters' => { :_method => 'put' },
- 'REQUEST_METHOD' => method.to_s.upcase
- )
-
- assert_equal method.to_s.upcase, request.method
+ assert_raise(ActionController::UnknownHttpMethod) do
+ stub_request('REQUEST_METHOD' => '_RANDOM_METHOD').method
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 269c7b4159..b8e20c52a0 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -4292,11 +4292,9 @@ 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
+ ActionDispatch::Routing::RouteSet.new.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ get '/test', to: ok, constraints: Object.new
end
end
end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
index b8479e8836..9f810cad01 100644
--- a/actionpack/test/dispatch/session/cache_store_test.rb
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -148,16 +148,15 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
def test_prevents_session_fixation
with_test_route_set do
- get '/get_session_value'
- assert_response :success
- assert_equal 'foo: nil', response.body
- session_id = cookies['_session_id']
+ assert_equal nil, @cache.read('_session_id:0xhax')
- reset!
+ cookies['_session_id'] = '0xhax'
+ get '/set_session_value'
- get '/set_session_value', :_session_id => session_id
assert_response :success
- assert_not_equal session_id, cookies['_session_id']
+ assert_not_equal '0xhax', cookies['_session_id']
+ assert_equal nil, @cache.read('_session_id:0xhax')
+ assert_equal({'foo' => 'bar'}, @cache.read("_session_id:#{cookies['_session_id']}"))
end
end
@@ -169,8 +168,8 @@ class CacheStoreTest < ActionDispatch::IntegrationTest
end
@app = self.class.build_app(set) do |middleware|
- cache = ActiveSupport::Cache::MemoryStore.new
- middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => cache
+ @cache = ActiveSupport::Cache::MemoryStore.new
+ middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => @cache
middleware.delete "ActionDispatch::ShowExceptions"
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 38bd234f37..323fbc285e 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -37,7 +37,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_response 500
assert_equal "500 error fixture\n", body
-
+
get "/bad_params", {}, {'action_dispatch.show_exceptions' => true}
assert_response 400
assert_equal "400 error fixture\n", body
@@ -92,6 +92,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
exceptions_app = lambda do |env|
assert_kind_of AbstractController::ActionNotFound, env["action_dispatch.exception"]
assert_equal "/404", env["PATH_INFO"]
+ assert_equal "/not_found_original_exception", env["action_dispatch.original_path"]
[404, { "Content-Type" => "text/plain" }, ["YOU FAILED BRO"]]
end
diff --git a/actionpack/test/dispatch/template_assertions_test.rb b/actionpack/test/dispatch/template_assertions_test.rb
new file mode 100644
index 0000000000..3c393f937b
--- /dev/null
+++ b/actionpack/test/dispatch/template_assertions_test.rb
@@ -0,0 +1,98 @@
+require 'abstract_unit'
+
+class AssertTemplateController < ActionController::Base
+ def render_with_partial
+ render partial: 'test/partial'
+ end
+
+ def render_with_template
+ render 'test/hello_world'
+ end
+
+ def render_with_layout
+ @variable_for_layout = nil
+ render 'test/hello_world', layout: "layouts/standard"
+ end
+
+ def render_with_file
+ render file: 'README.rdoc'
+ end
+
+ def render_nothing
+ head :ok
+ end
+end
+
+class AssertTemplateControllerTest < ActionDispatch::IntegrationTest
+ def test_template_reset_between_requests
+ get '/assert_template/render_with_template'
+ assert_template 'test/hello_world'
+
+ get '/assert_template/render_nothing'
+ assert_template nil
+ end
+
+ def test_partial_reset_between_requests
+ get '/assert_template/render_with_partial'
+ assert_template partial: 'test/_partial'
+
+ get '/assert_template/render_nothing'
+ assert_template partial: nil
+ end
+
+ def test_layout_reset_between_requests
+ get '/assert_template/render_with_layout'
+ assert_template layout: 'layouts/standard'
+
+ get '/assert_template/render_nothing'
+ assert_template layout: nil
+ end
+
+ def test_file_reset_between_requests
+ get '/assert_template/render_with_file'
+ assert_template file: 'README.rdoc'
+
+ get '/assert_template/render_nothing'
+ assert_template file: nil
+ end
+
+ def test_template_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_template'
+ session.assert_template 'test/hello_world'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template nil
+ end
+ end
+
+ def test_partial_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_partial'
+ session.assert_template partial: 'test/_partial'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template partial: nil
+ end
+ end
+
+ def test_layout_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_layout'
+ session.assert_template layout: 'layouts/standard'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template layout: nil
+ end
+ end
+
+ def test_file_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_file'
+ session.assert_template file: 'README.rdoc'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template file: nil
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 9f6381f118..55ebbd5143 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -18,6 +18,12 @@ module ActionDispatch
assert_equal "UTF-8", uf.original_filename.encoding.to_s
end
+ def test_filename_should_always_be_in_utf_8
+ uf = Http::UploadedFile.new(:filename => 'foo'.encode(Encoding::SHIFT_JIS),
+ :tempfile => Object.new)
+ assert_equal "UTF-8", uf.original_filename.encoding.to_s
+ end
+
def test_content_type
uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new)
assert_equal 'foo', uf.content_type
diff --git a/actionpack/test/fixtures/test/_changing_priority.html.erb b/actionpack/test/fixtures/test/_changing_priority.html.erb
deleted file mode 100644
index 3225efc49a..0000000000
--- a/actionpack/test/fixtures/test/_changing_priority.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-HTML \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_changing_priority.json.erb b/actionpack/test/fixtures/test/_changing_priority.json.erb
deleted file mode 100644
index 7fa41dce66..0000000000
--- a/actionpack/test/fixtures/test/_changing_priority.json.erb
+++ /dev/null
@@ -1 +0,0 @@
-JSON \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_counter.html.erb b/actionpack/test/fixtures/test/_counter.html.erb
deleted file mode 100644
index fd245bfc70..0000000000
--- a/actionpack/test/fixtures/test/_counter.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= counter_counter %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_customer.erb b/actionpack/test/fixtures/test/_customer.erb
deleted file mode 100644
index d8220afeda..0000000000
--- a/actionpack/test/fixtures/test/_customer.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello: <%= customer.name rescue "Anonymous" %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_customer_counter.erb b/actionpack/test/fixtures/test/_customer_counter.erb
deleted file mode 100644
index 3435979dba..0000000000
--- a/actionpack/test/fixtures/test/_customer_counter.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= customer_counter.name %><%= customer_counter_counter %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_customer_counter_with_as.erb b/actionpack/test/fixtures/test/_customer_counter_with_as.erb
deleted file mode 100644
index 1241eb604d..0000000000
--- a/actionpack/test/fixtures/test/_customer_counter_with_as.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= client.name %><%= client_counter %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_customer_greeting.erb b/actionpack/test/fixtures/test/_customer_greeting.erb
deleted file mode 100644
index 6acbcb20c4..0000000000
--- a/actionpack/test/fixtures/test/_customer_greeting.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= greeting %>: <%= customer_greeting.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_customer_with_var.erb b/actionpack/test/fixtures/test/_customer_with_var.erb
deleted file mode 100644
index 00047dd20e..0000000000
--- a/actionpack/test/fixtures/test/_customer_with_var.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= customer.name %> <%= customer.name %> <%= customer.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb b/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb
deleted file mode 100644
index 1cc8d41475..0000000000
--- a/actionpack/test/fixtures/test/_directory/_partial_with_locales.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello <%= name %>
diff --git a/actionpack/test/fixtures/test/_first_json_partial.json.erb b/actionpack/test/fixtures/test/_first_json_partial.json.erb
deleted file mode 100644
index 790ee896db..0000000000
--- a/actionpack/test/fixtures/test/_first_json_partial.json.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render :partial => "test/second_json_partial" %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_form.erb b/actionpack/test/fixtures/test/_form.erb
deleted file mode 100644
index 01107f1cb2..0000000000
--- a/actionpack/test/fixtures/test/_form.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= form.label :title %>
diff --git a/actionpack/test/fixtures/test/_hash_greeting.erb b/actionpack/test/fixtures/test/_hash_greeting.erb
deleted file mode 100644
index fc54a36f2a..0000000000
--- a/actionpack/test/fixtures/test/_hash_greeting.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= greeting %>: <%= hash_greeting[:first_name] %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_hash_object.erb b/actionpack/test/fixtures/test/_hash_object.erb
deleted file mode 100644
index 34a92c6a56..0000000000
--- a/actionpack/test/fixtures/test/_hash_object.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<%= hash_object[:first_name] %>
-<%= hash_object[:first_name].reverse %>
diff --git a/actionpack/test/fixtures/test/_hello.builder b/actionpack/test/fixtures/test/_hello.builder
deleted file mode 100644
index ef52f632d1..0000000000
--- a/actionpack/test/fixtures/test/_hello.builder
+++ /dev/null
@@ -1 +0,0 @@
-xm.hello \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_json_change_priority.json.erb b/actionpack/test/fixtures/test/_json_change_priority.json.erb
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionpack/test/fixtures/test/_json_change_priority.json.erb
+++ /dev/null
diff --git a/actionpack/test/fixtures/test/_labelling_form.erb b/actionpack/test/fixtures/test/_labelling_form.erb
deleted file mode 100644
index 1b95763165..0000000000
--- a/actionpack/test/fixtures/test/_labelling_form.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= labelling_form.label :title %>
diff --git a/actionpack/test/fixtures/test/_layout_for_partial.html.erb b/actionpack/test/fixtures/test/_layout_for_partial.html.erb
deleted file mode 100644
index 666efadbb6..0000000000
--- a/actionpack/test/fixtures/test/_layout_for_partial.html.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-Before (<%= name %>)
-<%= yield %>
-After \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb b/actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb
deleted file mode 100644
index 3a03a64e31..0000000000
--- a/actionpack/test/fixtures/test/_partial_for_use_in_layout.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Inside from partial (<%= name %>) \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_partial_html_erb.html.erb b/actionpack/test/fixtures/test/_partial_html_erb.html.erb
deleted file mode 100644
index 4b54875782..0000000000
--- a/actionpack/test/fixtures/test/_partial_html_erb.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= "partial.html.erb" %>
diff --git a/actionpack/test/fixtures/test/_partial_name_local_variable.erb b/actionpack/test/fixtures/test/_partial_name_local_variable.erb
deleted file mode 100644
index cc3a91c89f..0000000000
--- a/actionpack/test/fixtures/test/_partial_name_local_variable.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= partial_name_local_variable %>
diff --git a/actionpack/test/fixtures/test/_partial_only.erb b/actionpack/test/fixtures/test/_partial_only.erb
deleted file mode 100644
index a44b3eed40..0000000000
--- a/actionpack/test/fixtures/test/_partial_only.erb
+++ /dev/null
@@ -1 +0,0 @@
-only partial \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_partial_only_html.html b/actionpack/test/fixtures/test/_partial_only_html.html
deleted file mode 100644
index d2d630bd40..0000000000
--- a/actionpack/test/fixtures/test/_partial_only_html.html
+++ /dev/null
@@ -1 +0,0 @@
-only html partial \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_partial_with_partial.erb b/actionpack/test/fixtures/test/_partial_with_partial.erb
deleted file mode 100644
index ee0d5037b6..0000000000
--- a/actionpack/test/fixtures/test/_partial_with_partial.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<%= render 'test/partial' %>
-partial with partial
diff --git a/actionpack/test/fixtures/test/_person.erb b/actionpack/test/fixtures/test/_person.erb
deleted file mode 100644
index b2e5688956..0000000000
--- a/actionpack/test/fixtures/test/_person.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-Second: <%= name %>
-Third: <%= @name %>
diff --git a/actionpack/test/fixtures/test/_raise_indentation.html.erb b/actionpack/test/fixtures/test/_raise_indentation.html.erb
deleted file mode 100644
index f9a93728fe..0000000000
--- a/actionpack/test/fixtures/test/_raise_indentation.html.erb
+++ /dev/null
@@ -1,13 +0,0 @@
-<p>First paragraph</p>
-<p>Second paragraph</p>
-<p>Third paragraph</p>
-<p>Fourth paragraph</p>
-<p>Fifth paragraph</p>
-<p>Sixth paragraph</p>
-<p>Seventh paragraph</p>
-<p>Eight paragraph</p>
-<p>Ninth paragraph</p>
-<p>Tenth paragraph</p>
-<%= raise "error here!" %>
-<p>Eleventh paragraph</p>
-<p>Twelfth paragraph</p> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_second_json_partial.json.erb b/actionpack/test/fixtures/test/_second_json_partial.json.erb
deleted file mode 100644
index 5ebb7f1afd..0000000000
--- a/actionpack/test/fixtures/test/_second_json_partial.json.erb
+++ /dev/null
@@ -1 +0,0 @@
-Third level \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/action_talk_to_layout.erb b/actionpack/test/fixtures/test/action_talk_to_layout.erb
deleted file mode 100644
index 36e896daa8..0000000000
--- a/actionpack/test/fixtures/test/action_talk_to_layout.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<% @title = "Talking to the layout" -%>
-Action was here! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/calling_partial_with_layout.html.erb b/actionpack/test/fixtures/test/calling_partial_with_layout.html.erb
deleted file mode 100644
index ac44bc0d81..0000000000
--- a/actionpack/test/fixtures/test/calling_partial_with_layout.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => { :name => "David" }) %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/capturing.erb b/actionpack/test/fixtures/test/capturing.erb
deleted file mode 100644
index 1addaa40d9..0000000000
--- a/actionpack/test/fixtures/test/capturing.erb
+++ /dev/null
@@ -1,4 +0,0 @@
-<% days = capture do %>
- Dreamy days
-<% end %>
-<%= days %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/change_priority.html.erb b/actionpack/test/fixtures/test/change_priority.html.erb
deleted file mode 100644
index 5618977d05..0000000000
--- a/actionpack/test/fixtures/test/change_priority.html.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<%= render :partial => "test/json_change_priority", formats: :json %>
-HTML Template, but <%= render :partial => "test/changing_priority" %> partial \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/content_for.erb b/actionpack/test/fixtures/test/content_for.erb
deleted file mode 100644
index 1fb829f54c..0000000000
--- a/actionpack/test/fixtures/test/content_for.erb
+++ /dev/null
@@ -1 +0,0 @@
-<% content_for :title do -%>Putting stuff in the title!<% end -%>Great stuff! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/content_for_concatenated.erb b/actionpack/test/fixtures/test/content_for_concatenated.erb
deleted file mode 100644
index e65f629574..0000000000
--- a/actionpack/test/fixtures/test/content_for_concatenated.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-<% content_for :title, "Putting stuff "
- content_for :title, "in the title!" -%>
-Great stuff! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/content_for_with_parameter.erb b/actionpack/test/fixtures/test/content_for_with_parameter.erb
deleted file mode 100644
index aeb6f73ce0..0000000000
--- a/actionpack/test/fixtures/test/content_for_with_parameter.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<% content_for :title, "Putting stuff in the title!" -%>
-Great stuff! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/formatted_html_erb.html.erb b/actionpack/test/fixtures/test/formatted_html_erb.html.erb
deleted file mode 100644
index 1c64efabd8..0000000000
--- a/actionpack/test/fixtures/test/formatted_html_erb.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-formatted html erb \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/greeting.html.erb b/actionpack/test/fixtures/test/greeting.html.erb
deleted file mode 100644
index 62fb0293f0..0000000000
--- a/actionpack/test/fixtures/test/greeting.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<p>This is grand!</p>
diff --git a/actionpack/test/fixtures/test/greeting.xml.erb b/actionpack/test/fixtures/test/greeting.xml.erb
deleted file mode 100644
index 62fb0293f0..0000000000
--- a/actionpack/test/fixtures/test/greeting.xml.erb
+++ /dev/null
@@ -1 +0,0 @@
-<p>This is grand!</p>
diff --git a/actionpack/test/fixtures/test/hello,world.erb b/actionpack/test/fixtures/test/hello,world.erb
deleted file mode 100644
index bc8fa5e0ca..0000000000
--- a/actionpack/test/fixtures/test/hello,world.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello w*rld! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello.builder b/actionpack/test/fixtures/test/hello.builder
deleted file mode 100644
index a471553941..0000000000
--- a/actionpack/test/fixtures/test/hello.builder
+++ /dev/null
@@ -1,4 +0,0 @@
-xml.html do
- xml.p "Hello #{@name}"
- xml << render(:file => "test/greeting")
-end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_world_container.builder b/actionpack/test/fixtures/test/hello_world_container.builder
deleted file mode 100644
index e48d75c405..0000000000
--- a/actionpack/test/fixtures/test/hello_world_container.builder
+++ /dev/null
@@ -1,3 +0,0 @@
-xml.test do
- render :partial => 'hello', :locals => { :xm => xml }
-end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_world_from_rxml.builder b/actionpack/test/fixtures/test/hello_world_from_rxml.builder
deleted file mode 100644
index 619a97ba96..0000000000
--- a/actionpack/test/fixtures/test/hello_world_from_rxml.builder
+++ /dev/null
@@ -1,3 +0,0 @@
-xml.html do
- xml.p "Hello"
-end
diff --git a/actionpack/test/fixtures/test/hello_world_with_layout_false.erb b/actionpack/test/fixtures/test/hello_world_with_layout_false.erb
deleted file mode 100644
index 6769dd60bd..0000000000
--- a/actionpack/test/fixtures/test/hello_world_with_layout_false.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/html_template.html.erb b/actionpack/test/fixtures/test/html_template.html.erb
deleted file mode 100644
index 1bbc2b7f09..0000000000
--- a/actionpack/test/fixtures/test/html_template.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render :partial => "test/first_json_partial", formats: :json %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hyphen-ated.erb b/actionpack/test/fixtures/test/hyphen-ated.erb
deleted file mode 100644
index cd0875583a..0000000000
--- a/actionpack/test/fixtures/test/hyphen-ated.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello world!
diff --git a/actionpack/test/fixtures/test/list.erb b/actionpack/test/fixtures/test/list.erb
deleted file mode 100644
index 0a4bda58ee..0000000000
--- a/actionpack/test/fixtures/test/list.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= @test_unchanged = 'goodbye' %><%= render :partial => 'customer', :collection => @customers %><%= @test_unchanged %>
diff --git a/actionpack/test/fixtures/test/non_erb_block_content_for.builder b/actionpack/test/fixtures/test/non_erb_block_content_for.builder
deleted file mode 100644
index d539a425a4..0000000000
--- a/actionpack/test/fixtures/test/non_erb_block_content_for.builder
+++ /dev/null
@@ -1,4 +0,0 @@
-content_for :title do
- 'Putting stuff in the title!'
-end
-xml << "Great stuff!"
diff --git a/actionpack/test/fixtures/test/potential_conflicts.erb b/actionpack/test/fixtures/test/potential_conflicts.erb
deleted file mode 100644
index a5e964e359..0000000000
--- a/actionpack/test/fixtures/test/potential_conflicts.erb
+++ /dev/null
@@ -1,4 +0,0 @@
-First: <%= @name %>
-<%= render :partial => "person", :locals => { :name => "Stephan" } -%>
-Fourth: <%= @name %>
-Fifth: <%= name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/proper_block_detection.erb b/actionpack/test/fixtures/test/proper_block_detection.erb
deleted file mode 100644
index b55efbb25d..0000000000
--- a/actionpack/test/fixtures/test/proper_block_detection.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= @todo %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_file_from_template.html.erb b/actionpack/test/fixtures/test/render_file_from_template.html.erb
deleted file mode 100644
index fde9f4bb64..0000000000
--- a/actionpack/test/fixtures/test/render_file_from_template.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render :file => @path %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_file_with_locals_and_default.erb b/actionpack/test/fixtures/test/render_file_with_locals_and_default.erb
deleted file mode 100644
index 9b4900acc5..0000000000
--- a/actionpack/test/fixtures/test/render_file_with_locals_and_default.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= secret ||= 'one' %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb b/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb
deleted file mode 100644
index 0740b2d07c..0000000000
--- a/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hey HTML!
diff --git a/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb b/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb
deleted file mode 100644
index 4a11845cfe..0000000000
--- a/actionpack/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello HTML! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_implicit_js_template_without_layout.js.erb b/actionpack/test/fixtures/test/render_implicit_js_template_without_layout.js.erb
deleted file mode 100644
index 892ae5eca2..0000000000
--- a/actionpack/test/fixtures/test/render_implicit_js_template_without_layout.js.erb
+++ /dev/null
@@ -1 +0,0 @@
-alert('hello'); \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb b/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb
deleted file mode 100644
index 1461b95186..0000000000
--- a/actionpack/test/fixtures/test/render_partial_inside_directory.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render partial: 'test/_directory/partial_with_locales', locals: {'name' => 'Jane'} %>
diff --git a/actionpack/test/fixtures/test/render_to_string_test.erb b/actionpack/test/fixtures/test/render_to_string_test.erb
deleted file mode 100644
index 6e267e8634..0000000000
--- a/actionpack/test/fixtures/test/render_to_string_test.erb
+++ /dev/null
@@ -1 +0,0 @@
-The value of foo is: ::<%= @foo %>::
diff --git a/actionpack/test/fixtures/test/render_two_partials.html.erb b/actionpack/test/fixtures/test/render_two_partials.html.erb
deleted file mode 100644
index 3db6025860..0000000000
--- a/actionpack/test/fixtures/test/render_two_partials.html.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<%= render :partial => 'partial', :locals => {'first' => '1'} %>
-<%= render :partial => 'partial', :locals => {'second' => '2'} %>
diff --git a/actionpack/test/fixtures/test/using_layout_around_block.html.erb b/actionpack/test/fixtures/test/using_layout_around_block.html.erb
deleted file mode 100644
index 3d6661df9a..0000000000
--- a/actionpack/test/fixtures/test/using_layout_around_block.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render(:layout => "layout_for_partial", :locals => { :name => "David" }) do %>Inside from block<% end %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/with_html_partial.html.erb b/actionpack/test/fixtures/test/with_html_partial.html.erb
deleted file mode 100644
index d84d909d64..0000000000
--- a/actionpack/test/fixtures/test/with_html_partial.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<strong><%= render :partial => "partial_only_html" %></strong>
diff --git a/actionpack/test/fixtures/test/with_partial.html.erb b/actionpack/test/fixtures/test/with_partial.html.erb
deleted file mode 100644
index 7502364cf5..0000000000
--- a/actionpack/test/fixtures/test/with_partial.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<strong><%= render :partial => "partial_only" %></strong>
diff --git a/actionpack/test/fixtures/test/with_partial.text.erb b/actionpack/test/fixtures/test/with_partial.text.erb
deleted file mode 100644
index 5f068ebf27..0000000000
--- a/actionpack/test/fixtures/test/with_partial.text.erb
+++ /dev/null
@@ -1 +0,0 @@
-**<%= render :partial => "partial_only" %>**
diff --git a/actionpack/test/fixtures/test/with_xml_template.html.erb b/actionpack/test/fixtures/test/with_xml_template.html.erb
deleted file mode 100644
index e54a7cd001..0000000000
--- a/actionpack/test/fixtures/test/with_xml_template.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-<%= render :template => "test/greeting", :formats => :xml %>
diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb
index 0028aaa629..09ca7ff73b 100644
--- a/actionpack/test/routing/helper_test.rb
+++ b/actionpack/test/routing/helper_test.rb
@@ -26,6 +26,20 @@ module ActionDispatch
x.new.pond_duck_path Duck.new
end
end
+
+ def test_path_deprecation
+ rs = ::ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ resources :ducks
+ end
+
+ x = Class.new {
+ include rs.url_helpers(false)
+ }
+ assert_deprecated do
+ assert_equal '/ducks', x.new.ducks_path
+ end
+ end
end
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index a1710619a3..29e71d4cf4 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -27,6 +27,25 @@
*Kasper Timm Hansen*
+* Fix that render layout: 'messages/layout' should also be added to the dependency tracker tree.
+
+ *DHH*
+
+* Add `PartialIteration` object used when rendering collections.
+
+ The iteration object is available as the local variable
+ `#{template_name}_iteration` when rendering partials with collections.
+
+ It gives access to the `size` of the collection being iterated over,
+ the current `index` and two convenience methods `first?` and `last?`.
+
+ *Joel Junström*, *Lucas Uyezu*
+
+* Return an absolute instead of relative path from an asset url in the case
+ of the `asset_host` proc returning nil
+
+ *Jolyon Pawlyn*
+
* Fix `html_escape_once` to properly handle hex escape sequences (e.g. &#x1a2b;)
*John F. Douthat*
@@ -125,7 +144,7 @@
* Remove wrapping div with inline styles for hidden form fields.
We are dropping HTML 4.01 and XHTML strict compliance since input tags directly
- inside a form are valid HTML5, and the absense of inline styles help in validating
+ inside a form are valid HTML5, and the absence of inline styles help in validating
for Content Security Policy.
*Joost Baaij*
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 900f96255e..86c55ffb51 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -66,15 +66,6 @@ module ActionView #:nodoc:
# Headline: <%= headline %>
# First name: <%= person.first_name %>
#
- # If you need to find out whether a certain local variable has been assigned a value in a particular render call,
- # you need to use the following pattern:
- #
- # <% if local_assigns.has_key? :headline %>
- # Headline: <%= headline %>
- # <% end %>
- #
- # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
- #
# === Template caching
#
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 0ccf2515c5..e34bdd4a46 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -53,6 +53,12 @@ module ActionView
\s* # followed by optional spaces
/x
+ # Part of any hash containing the :layout key
+ LAYOUT_HASH_KEY = /
+ (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
+ \s* # followed by optional spaces
+ /x
+
# Matches:
# partial: "comments/comment", collection: @all_comments => "comments/comment"
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
@@ -65,9 +71,9 @@ module ActionView
# topics => "topics/topic"
# (message.topics) => "topics/topic"
RENDER_ARGUMENTS = /\A
- (?:\s*\(?\s*) # optional opening paren surrounded by spaces
- (?:.*?#{PARTIAL_HASH_KEY})? # optional hash, up to the partial key declaration
- (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
+ (?:\s*\(?\s*) # optional opening paren surrounded by spaces
+ (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
+ (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
/xm
def self.call(name, template)
@@ -85,8 +91,8 @@ module ActionView
attr_reader :name, :template
private :name, :template
- private
+ private
def source
template.source
end
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 72d79735ae..1f103786cb 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -60,7 +60,7 @@ module ActionView
def digest
Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.try :info, " Cache digest for #{template.inspect}: #{digest}"
+ logger.try :debug, " Cache digest for #{template.inspect}: #{digest}"
end
rescue ActionView::MissingTemplate
logger.try :error, " Couldn't find template for digesting: #{name}"
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 469f7c16bd..9e8d005ec7 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -203,7 +203,6 @@ module ActionView
request = self.request if respond_to?(:request)
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)
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
@@ -214,6 +213,7 @@ module ActionView
host = host % (Zlib.crc32(source) % 4)
end
+ host ||= request.base_url if request && options[:protocol] == :request
return unless host
if host =~ URI_REGEXP
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 48f42947db..8ade7c6a74 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -14,81 +14,81 @@ module ActionView
#
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
- # select("post", "category", Post::CATEGORIES, {include_blank: true})
+ # select("post", "category", Post::CATEGORIES, {include_blank: true})
#
- # could become:
+ # could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # </select>
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # </select>
#
- # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
+ # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
#
- # Example with @post.person_id => 2:
+ # Example with <tt>@post.person_id => 2</tt>:
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
#
- # could become:
+ # could become:
#
- # <select name="post[person_id]">
- # <option value="">None</option>
- # <option value="1">David</option>
- # <option value="2" selected="selected">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
+ # <select name="post[person_id]">
+ # <option value="">None</option>
+ # <option value="1">David</option>
+ # <option value="2" selected="selected">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
#
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
#
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
+ # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
#
- # could become:
+ # could become:
#
- # <select name="post[person_id]">
- # <option value="">Select Person</option>
- # <option value="1">David</option>
- # <option value="2">Sam</option>
- # <option value="3">Tobias</option>
- # </select>
+ # <select name="post[person_id]">
+ # <option value="">Select Person</option>
+ # <option value="1">David</option>
+ # <option value="2">Sam</option>
+ # <option value="3">Tobias</option>
+ # </select>
#
- # Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
- # option to be in the +html_options+ parameter.
+ # * <tt>:index</tt> - like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
+ # option to be in the +html_options+ parameter.
#
- # select("album[]", "genre", %w[rap rock country], {}, { index: nil })
+ # select("album[]", "genre", %w[rap rock country], {}, { index: nil })
#
- # becomes:
+ # becomes:
#
- # <select name="album[][genre]" id="album__genre">
- # <option value="rap">rap</option>
- # <option value="rock">rock</option>
- # <option value="country">country</option>
- # </select>
+ # <select name="album[][genre]" id="album__genre">
+ # <option value="rap">rap</option>
+ # <option value="rock">rock</option>
+ # <option value="country">country</option>
+ # </select>
#
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
#
- # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
+ # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
#
- # could become:
+ # could become:
#
- # <select name="post[category]">
- # <option></option>
- # <option>joke</option>
- # <option>poem</option>
- # <option disabled="disabled">restricted</option>
- # </select>
+ # <select name="post[category]">
+ # <option></option>
+ # <option>joke</option>
+ # <option>poem</option>
+ # <option disabled="disabled">restricted</option>
+ # </select>
#
- # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
+ # When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
+ # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
#
- # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
- # <select name="post[category_id]">
- # <option value="1" disabled="disabled">2008 stuff</option>
- # <option value="2" disabled="disabled">Christmas</option>
- # <option value="3">Jokes</option>
- # <option value="4">Poems</option>
- # </select>
+ # If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
+ # <select name="post[category_id]">
+ # <option value="1" disabled="disabled">2008 stuff</option>
+ # <option value="2" disabled="disabled">Christmas</option>
+ # <option value="3">Jokes</option>
+ # <option value="4">Poems</option>
+ # </select>
#
module FormOptionsHelper
# ERB::Util can mask some helpers like textilize. Make sure to include them.
@@ -152,11 +152,9 @@ module ActionView
# To prevent this the helper generates an auxiliary hidden field before
# every multiple select. The hidden field has the same name as multiple select and blank value.
#
- # This way, the client either sends only the hidden field (representing
- # the deselected multiple select box), or both fields. Since the HTML specification
- # says key/value pairs have to be sent in the same order they appear in the
- # form, and parameters extraction gets the last occurrence of any repeated
- # key in the query string, that works for ordinary forms.
+ # <b>Note:</b> The client either sends only the hidden field (representing
+ # the deselected multiple select box), or both fields. This means that the resulting array
+ # always contains a blank string.
#
# In case if you don't want the helper to generate this hidden field you can specify
# <tt>include_hidden: false</tt> option.
@@ -463,21 +461,7 @@ module ActionView
end
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
- # wraps them with <tt><optgroup></tt> tags.
- #
- # Parameters:
- # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
- # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
- # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
- # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
- # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
- # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
- # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
- #
- # Options:
- # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
- # prepends an option with a generic prompt - "Please select" - or the given prompt string.
- # * <tt>:divider</tt> - the divider for the options groups.
+ # wraps them with <tt><optgroup></tt> tags:
#
# grouped_options = [
# ['North America',
@@ -504,22 +488,36 @@ module ActionView
# <option value="France">France</option>
# </optgroup>
#
- # grouped_options = [
- # [['United States','US'], 'Canada'],
- # ['Denmark','Germany','France']
- # ]
- # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ # Parameters:
+ # * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
+ # <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
+ # nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
+ # Ex. ["North America",[["United States","US"],["Canada","CA"]]]
+ # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
+ # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
+ # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
#
- # Possible output:
- # <optgroup label="---------">
- # <option value="US">United States</option>
- # <option value="Canada">Canada</option>
- # </optgroup>
- # <optgroup label="---------">
- # <option value="Denmark">Denmark</option>
- # <option value="Germany">Germany</option>
- # <option value="France">France</option>
- # </optgroup>
+ # Options:
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
+ # prepends an option with a generic prompt - "Please select" - or the given prompt string.
+ # * <tt>:divider</tt> - the divider for the options groups.
+ #
+ # grouped_options = [
+ # [['United States','US'], 'Canada'],
+ # ['Denmark','Germany','France']
+ # ]
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ #
+ # Possible output:
+ # <optgroup label="---------">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
+ # <optgroup label="---------">
+ # <option value="Denmark">Denmark</option>
+ # <option value="Germany">Germany</option>
+ # <option value="France">France</option>
+ # </optgroup>
#
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index b0d9c7c7f9..f03362d0f5 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -17,7 +17,7 @@ module ActionView #:nodoc:
stringish.to_s.html_safe
end
- # This method returns a html safe string similar to what <tt>Array#join</tt>
+ # This method returns an html safe string similar to what <tt>Array#join</tt>
# 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.
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index ebfc35a4c7..6cd6e858dd 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -13,13 +13,13 @@ module ActionView
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
# * <tt>:plain</tt> - Renders the text passed in out. Setting the content
- # type as <tt>text/plain</tt>.
+ # type as <tt>text/plain</tt>.
# * <tt>:html</tt> - Renders the html safe string passed in out, otherwise
- # performs html escape on the string first. Setting the content type as
- # <tt>text/html</tt>.
+ # performs html escape on the string first. Setting the content type as
+ # <tt>text/html</tt>.
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
- # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
- # object.
+ # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt>
+ # object.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 00881d9978..180900cc8d 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -3,7 +3,7 @@ module ActionView
module Tags # :nodoc:
class Select < Base # :nodoc:
def initialize(object_name, method_name, template_object, choices, options, html_options)
- @choices = block_given? ? template_object.capture { yield } : choices
+ @choices = block_given? ? template_object.capture { yield || "" } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
@html_options = html_options
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index 6c8d9cb5bf..9047dbdd85 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -13,11 +13,11 @@ module ActionView
end
def render_template(event)
- return unless logger.info?
- message = " Rendered #{from_rails_root(event.payload[:identifier])}"
- message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
- message << " (#{event.duration.round(1)}ms)"
- info(message)
+ info do
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}"
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
+ message << " (#{event.duration.round(1)}ms)"
+ end
end
alias :render_partial :render_template
alias :render_collection :render_template
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 5fff6b0771..ea687d9cca 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -66,10 +66,7 @@ module ActionView
def self.get(details)
if details[:formats]
details = details.dup
- syms = Set.new Mime::SET.symbols
- details[:formats] = details[:formats].select { |v|
- syms.include? v
- }
+ details[:formats] &= Mime::SET.symbols
end
@details_keys[details] ||= new
end
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index 73c19a0ae2..1f122f9bc6 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -29,8 +29,9 @@ module ActionView
def extract_details(options)
@lookup_context.registered_details.each_with_object({}) do |key, details|
- next unless value = options[key]
- details[key] = Array(value)
+ value = options[key]
+
+ details[key] = Array(value) if value
end
end
@@ -41,6 +42,7 @@ module ActionView
def prepend_formats(formats)
formats = Array(formats)
return if formats.empty? || @lookup_context.html_fallback_for_js
+
@lookup_context.formats = formats | @lookup_context.formats
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 36f17f01fd..0407632435 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,6 +1,33 @@
require 'thread_safe'
module ActionView
+ class PartialIteration
+ # The number of iterations that will be done by the partial.
+ attr_reader :size
+
+ # The current iteration of the partial.
+ attr_reader :index
+
+ def initialize(size)
+ @size = size
+ @index = 0
+ end
+
+ # Check if this is the first iteration of the partial.
+ def first?
+ index == 0
+ end
+
+ # Check if this is the last iteration of the partial.
+ def last?
+ index == size - 1
+ end
+
+ def iterate! # :nodoc:
+ @index += 1
+ end
+ end
+
# = Action View Partials
#
# There's also a convenience method for rendering sub templates within the current controller that depends on a
@@ -56,8 +83,12 @@ module ActionView
# <%= render partial: "ad", collection: @advertisements %>
#
# This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An
- # iteration counter will automatically be made available to the template with a name of the form
- # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+.
+ # iteration object will automatically be made available to the template with a name of the form
+ # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
+ # the collection and the total size of the collection. The iteration object also has two convenience methods,
+ # +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+.
+ # For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's
+ # +index+ method.
#
# The <tt>:as</tt> option may be used when rendering partials.
#
@@ -281,6 +312,8 @@ module ActionView
end
end
+ private
+
def render_collection
return nil if @collection.blank?
@@ -322,25 +355,27 @@ module ActionView
# respond to +to_partial_path+ in order to setup the path.
def setup(context, options, block)
@view = context
- partial = options[:partial]
-
@options = options
- @locals = options[:locals] || {}
@block = block
+
+ @locals = options[:locals] || {}
@details = extract_details(options)
prepend_formats(options[:formats])
+ partial = options[:partial]
+
if String === partial
@object = options[:object]
+ @collection = collection_from_options
@path = partial
- @collection = collection
else
@object = partial
+ @collection = collection_from_object || collection_from_options
- if @collection = collection_from_object || collection
+ if @collection
paths = @collection_data = @collection.map { |o| partial_path(o) }
- @path = paths.uniq.size == 1 ? paths.first : nil
+ @path = paths.uniq.one? ? paths.first : nil
else
@path = partial_path
end
@@ -352,7 +387,7 @@ module ActionView
end
if @path
- @variable, @variable_counter = retrieve_variable(@path, as)
+ @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
@template_keys = retrieve_template_keys
else
paths.map! { |path| retrieve_variable(path, as).unshift(path) }
@@ -361,7 +396,7 @@ module ActionView
self
end
- def collection
+ def collection_from_options
if @options.key?(:collection)
collection = @options[:collection]
collection.respond_to?(:to_ary) ? collection.to_ary : []
@@ -373,9 +408,7 @@ module ActionView
end
def find_partial
- if path = @path
- find_template(path, @template_keys)
- end
+ find_template(@path, @template_keys) if @path
end
def find_template(path, locals)
@@ -385,19 +418,22 @@ module ActionView
def collection_with_template
view, locals, template = @view, @locals, @template
- as, counter = @variable, @variable_counter
+ as, counter, iteration = @variable, @variable_counter, @variable_iteration
if layout = @options[:layout]
layout = find_template(layout, @template_keys)
end
- index = -1
+ partial_iteration = PartialIteration.new(@collection.size)
+ locals[iteration] = partial_iteration
+
@collection.map do |object|
- locals[as] = object
- locals[counter] = (index += 1)
+ locals[as] = object
+ locals[counter] = partial_iteration.index
content = template.render(view, locals)
content = layout.render(view, locals) { content } if layout
+ partial_iteration.iterate!
content
end
end
@@ -407,16 +443,20 @@ module ActionView
cache = {}
keys = @locals.keys
- index = -1
+ partial_iteration = PartialIteration.new(@collection.size)
+
@collection.map do |object|
- index += 1
- path, as, counter = collection_data[index]
+ index = partial_iteration.index
+ path, as, counter, iteration = collection_data[index]
- locals[as] = object
- locals[counter] = index
+ locals[as] = object
+ locals[counter] = index
+ locals[iteration] = partial_iteration
template = (cache[path] ||= find_template(path, keys + [as, counter]))
- template.render(view, locals)
+ content = template.render(view, locals)
+ partial_iteration.iterate!
+ content
end
end
@@ -466,8 +506,11 @@ module ActionView
def retrieve_template_keys
keys = @locals.keys
- keys << @variable if @object || @collection
- keys << @variable_counter if @collection
+ keys << @variable if @object || @collection
+ if @collection
+ keys << @variable_counter
+ keys << @variable_iteration
+ end
keys
end
@@ -477,8 +520,11 @@ module ActionView
raise_invalid_identifier(path) unless base =~ /\A_?([a-z]\w*)(\.\w+)*\z/
$1.to_sym
end
- variable_counter = :"#{variable}_counter" if @collection
- [variable, variable_counter]
+ if @collection
+ variable_counter = :"#{variable}_counter"
+ variable_iteration = :"#{variable}_iteration"
+ end
+ [variable, variable_counter, variable_iteration]
end
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index be17097428..f3a48ecfa0 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -6,19 +6,18 @@ module ActionView
@view = context
@details = extract_details(options)
template = determine_template(options)
- context = @lookup_context
prepend_formats(template.formats)
- unless context.rendered_format
- context.rendered_format = template.formats.first || formats.first
- end
+ @lookup_context.rendered_format ||= (template.formats.first || formats.first)
render_template(template, options[:layout], options[:locals])
end
+ private
+
# Determine the template to be rendered using the given options.
- def determine_template(options) #:nodoc:
+ def determine_template(options)
keys = options.fetch(:locals, {}).keys
if options.key?(:body)
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index c92d090cce..81d5836a8c 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -35,12 +35,13 @@ module ActionView
module ClassMethods
def view_context_class
@view_context_class ||= begin
- routes = respond_to?(:_routes) && _routes
+ include_path_helpers = supports_path?
+ routes = respond_to?(:_routes) && _routes
helpers = respond_to?(:_helpers) && _helpers
Class.new(ActionView::Base) do
if routes
- include routes.url_helpers
+ include routes.url_helpers(include_path_helpers)
include routes.mounted_helpers
end
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 4c65860423..7edfc436a6 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -264,7 +264,7 @@ module ActionView
def method_missing(selector, *args)
if @controller.respond_to?(:_routes) &&
- ( @controller._routes.named_routes.helpers.include?(selector) ||
+ ( @controller._routes.named_routes.route_defined?(selector) ||
@controller._routes.mounted_helpers.method_defined?(selector) )
@controller.__send__(selector, *args)
else
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 7c71fdabd1..923e637f11 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -338,3 +338,10 @@ end
def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index ab7b961ed2..b3b51ae583 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -536,6 +536,14 @@ class TestController < ApplicationController
render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer
end
+ def partial_collection_with_iteration
+ render partial: "customer_iteration", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ]
+ end
+
+ def partial_collection_with_as_and_iteration
+ render partial: "customer_iteration_with_as", collection: [ Customer.new("david"), Customer.new("mary"), Customer.new('christine') ], as: :client
+ end
+
def partial_collection_with_counter
render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
@@ -839,7 +847,7 @@ class RenderTest < ActionController::TestCase
def test_render_text_with_nil
get :render_text_with_nil
assert_response 200
- assert_equal ' ', @response.body
+ assert_equal '', @response.body
end
# :ported:
@@ -1027,7 +1035,7 @@ class RenderTest < ActionController::TestCase
def test_rendering_nothing_on_layout
get :rendering_nothing_on_layout
- assert_equal " ", @response.body
+ assert_equal '', @response.body
end
def test_render_to_string_doesnt_break_assigns
@@ -1237,6 +1245,16 @@ class RenderTest < ActionController::TestCase
assert_equal "david david davidmary mary mary", @response.body
end
+ def test_partial_collection_with_iteration
+ get :partial_collection_with_iteration
+ assert_equal "3-0: david-first3-1: mary3-2: christine-last", @response.body
+ end
+
+ def test_partial_collection_with_as_and_iteration
+ get :partial_collection_with_as_and_iteration
+ assert_equal "3-0: david-first3-1: mary3-2: christine-last", @response.body
+ end
+
def test_partial_collection_with_counter
get :partial_collection_with_counter
assert_equal "david0mary1", @response.body
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index fef27ef492..e220dcb8cb 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -158,34 +158,38 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_with_nil
with_test_routes do
- assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ exception = assert_raise ArgumentError do
polymorphic_url(nil)
end
+ assert_equal "Nil location provided. Can't build URI.", exception.message
end
end
def test_with_empty_list
with_test_routes do
- assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ exception = assert_raise ArgumentError do
polymorphic_url([])
end
+ assert_equal "Nil location provided. Can't build URI.", exception.message
end
end
def test_with_nil_id
with_test_routes do
- assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ exception = assert_raise ArgumentError do
polymorphic_url({ :id => nil })
end
+ assert_equal "Nil location provided. Can't build URI.", exception.message
end
end
def test_with_nil_in_list
with_test_routes do
- assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ exception = assert_raise ArgumentError do
@series.save
polymorphic_url([nil, @series])
end
+ assert_equal "Nil location provided. Can't build URI.", exception.message
end
end
diff --git a/actionview/test/fixtures/actionpack/test/_customer_iteration.erb b/actionview/test/fixtures/actionpack/test/_customer_iteration.erb
new file mode 100644
index 0000000000..fb530b04a7
--- /dev/null
+++ b/actionview/test/fixtures/actionpack/test/_customer_iteration.erb
@@ -0,0 +1 @@
+<%= customer_iteration_iteration.size %>-<%= customer_iteration_iteration.index %>: <%= customer_iteration.name %><%= '-first' if customer_iteration_iteration.first? %><%= '-last' if customer_iteration_iteration.last? %> \ No newline at end of file
diff --git a/actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb b/actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb
new file mode 100644
index 0000000000..57297d0564
--- /dev/null
+++ b/actionview/test/fixtures/actionpack/test/_customer_iteration_with_as.erb
@@ -0,0 +1 @@
+<%= client_iteration.size %>-<%= client_iteration.index %>: <%= client.name %><%= '-first' if client_iteration.first? %><%= '-last' if client_iteration.last? %> \ No newline at end of file
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 343681b5a9..d789a5ca27 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -546,6 +546,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal "http://cdn.example.com/images/file.png", image_path("file.png")
end
+ def test_image_url_with_asset_host_proc_returning_nil
+ @controller.config.asset_host = Proc.new { nil }
+ @controller.request = Struct.new(:base_url, :script_name).new("http://www.example.com", nil)
+
+ assert_equal "/images/rails.png", image_path("rails.png")
+ assert_equal "http://www.example.com/images/rails.png", image_url("rails.png")
+ end
+
def test_caching_image_path_with_caching_and_proc_asset_host_using_request
@controller.config.asset_host = Proc.new do |source, request|
if request.ssl?
diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb
index 6c780f2297..bb375076c6 100644
--- a/actionview/test/template/dependency_tracker_test.rb
+++ b/actionview/test/template/dependency_tracker_test.rb
@@ -60,6 +60,21 @@ class ERBTrackerTest < Minitest::Test
assert_equal ["messages/message123"], tracker.dependencies
end
+ def test_dependency_of_template_partial_with_layout
+ skip # FIXME: Needs to be fixed properly, right now we can only match one dependency per line. Need multiple!
+ template = FakeTemplate.new("<%# render partial: 'messages/show', layout: 'messages/layout' %>", :erb)
+ tracker = make_tracker("multiple/_dependencies", template)
+
+ assert_equal ["messages/layout", "messages/show"], tracker.dependencies
+ end
+
+ def test_dependency_of_template_layout_standalone
+ template = FakeTemplate.new("<%# render layout: 'messages/layout' do %>", :erb)
+ tracker = make_tracker("messages/layout", template)
+
+ assert_equal ["messages/layout"], tracker.dependencies
+ end
+
def test_finds_dependency_in_correct_directory
template = FakeTemplate.new("<%# render(message.topic) %>", :erb)
tracker = make_tracker("messages/_message", template)
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 3e39dadcf1..a9f137aec6 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -694,6 +694,13 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_text_area_with_value_before_type_cast
+ assert_dom_equal(
+ %{<textarea id="post_id" name="post[id]">\n123</textarea>},
+ text_area("post", "id")
+ )
+ end
+
def test_text_area_with_html_entities
@post.body = "The HTML Entity for & is &amp;"
assert_dom_equal(
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index fbafb7aa08..d25fa3706f 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -591,6 +591,19 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_under_fields_for_with_block_without_options
+ @post = Post.new
+
+ output_buffer = fields_for :post, @post do |f|
+ concat(f.select(:category) {})
+ end
+
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"></select>",
+ output_buffer
+ )
+ end
+
def test_select_with_multiple_to_add_hidden_input
output_buffer = select(:post, :category, "", {}, :multiple => true)
assert_dom_equal(
diff --git a/actionview/test/template/partial_iteration_test.rb b/actionview/test/template/partial_iteration_test.rb
new file mode 100644
index 0000000000..695f9f1bef
--- /dev/null
+++ b/actionview/test/template/partial_iteration_test.rb
@@ -0,0 +1,33 @@
+require 'abstract_unit'
+require 'action_view/renderer/partial_renderer'
+
+class PartialIterationTest < ActiveSupport::TestCase
+ def test_has_size_and_index
+ iteration = ActionView::PartialIteration.new 3
+ assert_equal 0, iteration.index, "should be at the first index"
+ assert_equal 3, iteration.size, "should have the size"
+ end
+
+ def test_first_is_true_when_current_is_at_the_first_index
+ iteration = ActionView::PartialIteration.new 3
+ assert iteration.first?, "first when current is 0"
+ end
+
+ def test_first_is_false_unless_current_is_at_the_first_index
+ iteration = ActionView::PartialIteration.new 3
+ iteration.iterate!
+ assert !iteration.first?, "not first when current is 1"
+ end
+
+ def test_last_is_true_when_current_is_at_the_last_index
+ iteration = ActionView::PartialIteration.new 3
+ iteration.iterate!
+ iteration.iterate!
+ assert iteration.last?, "last when current is 2"
+ end
+
+ def test_last_is_false_unless_current_is_at_the_last_index
+ iteration = ActionView::PartialIteration.new 3
+ assert !iteration.last?, "not last when current is 0"
+ end
+end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 67f1aabbd2..85817119ba 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -256,7 +256,7 @@ module RenderTestCases
end
def test_render_partial_collection_without_as
- assert_equal "local_inspector,local_inspector_counter",
+ assert_equal "local_inspector,local_inspector_counter,local_inspector_iteration",
@view.render(:partial => "test/local_inspector", :collection => [ Customer.new("mary") ])
end
@@ -324,11 +324,16 @@ module RenderTestCases
@controller_view.render(customers, :greeting => "Hello")
end
+ def test_render_partial_using_collection_without_path
+ assert_equal "hi good customer: david0", @controller_view.render([ GoodCustomer.new("david") ], greeting: "hi")
+ end
+
def test_render_partial_without_object_or_collection_does_not_generate_partial_name_local_variable
exception = assert_raises ActionView::Template::Error do
@controller_view.render("partial_name_local_variable")
end
- assert_match "undefined local variable or method `partial_name_local_variable'", exception.message
+ assert_instance_of NameError, exception.original_exception
+ assert_equal :partial_name_local_variable, exception.original_exception.name
end
# TODO: The reason for this test is unclear, improve documentation
@@ -387,6 +392,14 @@ module RenderTestCases
ActionView::Template.unregister_template_handler :foo
end
+ def test_render_body
+ assert_equal 'some body', @view.render(body: 'some body')
+ end
+
+ def test_render_plain
+ assert_equal 'some plaintext', @view.render(plain: 'some plaintext')
+ 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
diff --git a/actionview/test/template/test_case_test.rb b/actionview/test/template/test_case_test.rb
index 4ee0930341..4582fa13ee 100644
--- a/actionview/test/template/test_case_test.rb
+++ b/actionview/test/template/test_case_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'rails/engine'
module ActionView
@@ -223,7 +224,7 @@ module ActionView
test "is able to use mounted routes" do
with_routing do |set|
- app = Class.new do
+ app = Class.new(Rails::Engine) do
def self.routes
@routes ||= ActionDispatch::Routing::RouteSet.new
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 2565b24c97..c14b0688c7 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,4 +1,28 @@
-* Added `undo_changes` method to `ActiveModel::Dirty` API to restore all the
+* Passwords with spaces only allowed in `ActiveModel::SecurePassword`.
+
+ Presence validation can be used to restore old behavior.
+
+ *Yevhene Shemet*
+
+* Validate options passed to `ActiveModel::Validations.validate`.
+
+ Preventing, in many cases, the simple mistake of using `validate` instead of `validates`.
+
+ *Sonny Michaud*
+
+* Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
+
+ These methods may cause confusion with the `reset_changes` that behaves differently
+ of them.
+
+* Deprecate `ActiveModel::Dirty#reset_changes` in favor of `#clear_changes_information`.
+
+ This method name is causing confusion with the `reset_#{attribute}`
+ methods. While `reset_name` set the value of the name attribute for the
+ previous value `reset_changes` only discard the changes and previous
+ changes.
+
+* Added `restore_attributes` method to `ActiveModel::Dirty` API to restore all the
changed values to the previous data.
*Igor G.*
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index dc6088a39a..d11243c4c0 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -15,9 +15,9 @@ module ActiveModel
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
# attribute.
# * Call <tt>changes_applied</tt> after the changes are persisted.
- # * Call <tt>reset_changes</tt> when you want to reset the changes
+ # * Call <tt>clear_changes_information</tt> when you want to reset the changes
# information.
- # * Call <tt>undo_changes</tt> when you want to restore previous data.
+ # * Call <tt>restore_attributes</tt> when you want to restore previous data.
#
# A minimal implementation could be:
#
@@ -37,15 +37,18 @@ module ActiveModel
#
# def save
# # do persistence work
+ #
# changes_applied
# end
#
# def reload!
- # reset_changes
+ # # get the values from the persistence layer
+ #
+ # clear_changes_information
# end
#
# def rollback!
- # undo_changes
+ # restore_attributes
# end
# end
#
@@ -113,6 +116,7 @@ module ActiveModel
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
attribute_method_affix prefix: 'reset_', suffix: '!'
+ attribute_method_affix prefix: 'restore_', suffix: '!'
end
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
@@ -176,6 +180,11 @@ module ActiveModel
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
+ # Restore all previous data of the provided attributes.
+ def restore_attributes(attributes = changed)
+ attributes.each { |attr| restore_attribute! attr }
+ end
+
private
# Removes current changes and makes them accessible through +previous_changes+.
@@ -184,15 +193,15 @@ module ActiveModel
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Removes all dirty data: current changes and previous changes.
- def reset_changes # :doc:
+ # Clear all dirty data: current changes and previous changes.
+ def clear_changes_information # :doc:
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Restore all previous data.
- def undo_changes # :doc:
- changed_attributes.each_key { |attr| reset_attribute! attr }
+ def reset_changes
+ ActiveSupport::Deprecation.warn "#reset_changes is deprecated and will be removed on Rails 5. Please use #clear_changes_information instead."
+ clear_changes_information
end
# Handle <tt>*_change</tt> for +method_missing+.
@@ -215,6 +224,13 @@ module ActiveModel
# Handle <tt>reset_*!</tt> for +method_missing+.
def reset_attribute!(attr)
+ ActiveSupport::Deprecation.warn "#reset_#{attr}! is deprecated and will be removed on Rails 5. Please use #restore_#{attr}! instead."
+
+ restore_attribute!(attr)
+ end
+
+ # Handle <tt>restore_*!</tt> for +method_missing+.
+ def restore_attribute!(attr)
if attribute_changed?(attr)
__send__("#{attr}=", changed_attributes[attr])
changed_attributes.delete(attr)
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 917d3b9142..1d025beeef 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -23,7 +23,7 @@ module ActiveModel
# attr_reader :errors
#
# def validate!
- # errors.add(:name, "cannot be nil") if name == nil
+ # errors.add(:name, "cannot be nil") if name.nil?
# end
#
# # The following methods are needed to be minimally implemented
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 86f5c96af9..241e88deeb 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -215,10 +215,8 @@ module ActiveModel
# provided method below, or rolling your own is required.
module Naming
def self.extended(base) #:nodoc:
- base.class_eval do
- remove_possible_method(:model_name)
- delegate :model_name, to: :class
- end
+ base.remove_possible_method :model_name
+ base.delegate :model_name, to: :class
end
# Returns an ActiveModel::Name object for module. It can be
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 7e179cf4b7..f6ad35769f 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -105,7 +105,7 @@ module ActiveModel
attr_reader :password
# Encrypts the password into the +password_digest+ attribute, only if the
- # new password is not blank.
+ # new password is not empty.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
@@ -119,7 +119,7 @@ module ActiveModel
def password=(unencrypted_password)
if unencrypted_password.nil?
self.password_digest = nil
- elsif unencrypted_password.present?
+ elsif !unencrypted_password.empty?
@password = unencrypted_password
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index cf97f45dba..f67a3be5c1 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -141,6 +141,11 @@ module ActiveModel
# value.
def validate(*args, &block)
options = args.extract_options!
+
+ if args.all? { |arg| arg.is_a?(Symbol) }
+ options.assert_valid_keys([:on, :if, :unless])
+ end
+
if options.key?(:on)
options = options.dup
options[:if] = Array(options[:if])
@@ -148,6 +153,7 @@ module ActiveModel
Array(options[:on]).include?(o.validation_context)
}
end
+
args << options
set_callback(:validate, *args, &block)
end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 65cb1e5a88..0116de68ab 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -79,7 +79,7 @@ module ActiveModel
# include ActiveModel::Validations
# attr_accessor :title
#
- # validates :title, presence: true
+ # validates :title, presence: true, title: true
# end
#
# It can be useful to access the class that is using that validator when there are prerequisites such
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 562fadbb85..db2cd885e2 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -43,11 +43,11 @@ class DirtyTest < ActiveModel::TestCase
end
def reload
- reset_changes
+ clear_changes_information
end
- def rollback
- undo_changes
+ def deprecated_reload
+ reset_changes
end
end
@@ -111,7 +111,7 @@ class DirtyTest < ActiveModel::TestCase
test "resetting attribute" do
@model.name = "Bob"
- @model.reset_name!
+ @model.restore_name!
assert_nil @model.name
assert !@model.name_changed?
end
@@ -181,17 +181,48 @@ class DirtyTest < ActiveModel::TestCase
assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
end
- test "undo_changes should restore all previous data" do
+ test "reset_changes is deprecated" do
+ @model.name = 'Dmitry'
+ @model.name_changed?
+ @model.save
+ @model.name = 'Bob'
+
+ assert_equal [nil, 'Dmitry'], @model.previous_changes['name']
+ assert_equal 'Dmitry', @model.changed_attributes['name']
+
+ assert_deprecated do
+ @model.deprecated_reload
+ end
+
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
+ end
+
+ test "restore_attributes should restore all previous data" do
@model.name = 'Dmitry'
@model.color = 'Red'
@model.save
@model.name = 'Bob'
@model.color = 'White'
- @model.rollback
+ @model.restore_attributes
assert_not @model.changed?
assert_equal 'Dmitry', @model.name
assert_equal 'Red', @model.color
end
+
+ test "restore_attributes can restore only some attributes" do
+ @model.name = 'Dmitry'
+ @model.color = 'Red'
+ @model.save
+ @model.name = 'Bob'
+ @model.color = 'White'
+
+ @model.restore_attributes(['name'])
+
+ assert @model.changed?
+ assert_equal 'Dmitry', @model.name
+ assert_equal 'White', @model.color
+ end
end
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 522a7cebb4..5e80353836 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -11,3 +11,10 @@ ActiveSupport::Deprecation.debug = true
I18n.enforce_available_locales = false
require 'active_support/testing/autorun'
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 6b21bc68fa..6d56c8344a 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -40,6 +40,11 @@ class SecurePasswordTest < ActiveModel::TestCase
assert @user.valid?(:create), 'user should be valid'
end
+ test "create a new user with validation and a spaces only password" do
+ @user.password = ' ' * 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'
@@ -105,6 +110,11 @@ class SecurePasswordTest < ActiveModel::TestCase
assert @existing_user.valid?(:update), 'user should be valid'
end
+ test "updating an existing user with validation and a spaces only password" do
+ @user.password = ' ' * 72
+ assert @user.valid?(:update), 'user should be valid'
+ end
+
test "updating an existing user with validation and a blank password and password_confirmation" do
@existing_user.password = ''
@existing_user.password_confirmation = ''
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 4fee704ef5..ba0aacc2a5 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -4,7 +4,6 @@ require 'cases/helper'
require 'models/topic'
require 'models/reply'
require 'models/custom_reader'
-require 'models/automobile'
require 'active_support/json'
require 'active_support/xml_mini'
@@ -167,6 +166,13 @@ class ValidationsTest < ActiveModel::TestCase
end
end
+ def test_invalid_options_to_validate
+ assert_raises(ArgumentError) do
+ # A common mistake -- we meant to call 'validates'
+ Topic.validate :title, presence: true
+ end
+ end
+
def test_errors_conversions
Topic.validates_presence_of %w(title content)
t = Topic.new
@@ -283,25 +289,30 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_validations_on_the_instance_level
- auto = Automobile.new
+ Topic.validates :title, :author_name, presence: true
+ Topic.validates :content, length: { minimum: 10 }
- assert auto.invalid?
- assert_equal 3, auto.errors.size
-
- auto.make = 'Toyota'
- auto.model = 'Corolla'
- auto.approved = '1'
+ topic = Topic.new
+ assert topic.invalid?
+ assert_equal 3, topic.errors.size
- assert auto.valid?
+ topic.title = 'Some Title'
+ topic.author_name = 'Some Author'
+ topic.content = 'Some Content Whose Length is more than 10.'
+ assert topic.valid?
end
def test_validate
- auto = Automobile.new
+ Topic.validate do
+ validates_presence_of :title, :author_name
+ validates_length_of :content, minimum: 10
+ end
- assert_empty auto.errors
+ topic = Topic.new
+ assert_empty topic.errors
- auto.validate
- assert_not_empty auto.errors
+ topic.validate
+ assert_not_empty topic.errors
end
def test_strict_validation_in_validates
diff --git a/activemodel/test/models/automobile.rb b/activemodel/test/models/automobile.rb
deleted file mode 100644
index 4df2fe8b3a..0000000000
--- a/activemodel/test/models/automobile.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-class Automobile
- include ActiveModel::Validations
-
- validate :validations
-
- attr_accessor :make, :model, :approved
-
- def validations
- validates_presence_of :make
- validates_length_of :model, within: 2..10
- validates_acceptance_of :approved, allow_nil: false
- end
-end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 971bef913f..ea951fdfd1 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,158 @@
+* When calling `update_columns` on a record that is not persisted, the error
+ message now reflects whether that object is a new record or has been
+ destroyed.
+
+ *Lachlan Sylvester*
+
+* Define `id_was` to get the previous value of the primary key.
+
+ Currently when we call id_was and we have a custom primary key name
+ Active Record will return the current value of the primary key. This
+ make impossible to correctly do an update operation if you change the
+ id.
+
+ Fixes #16413.
+
+ *Rafael Mendonça França*
+
+* Deprecate `DatabaseTasks.load_schema` to act on the current connection.
+ Use `.load_schema_current` instead. In the future `load_schema` will
+ require the `configuration` to act on as an argument.
+
+ *Yves Senn*
+
+* Fixed automatic maintaining test schema to properly handle sql structure
+ schema format.
+
+ Fixes #15394.
+
+ *Wojciech Wnętrzak*
+
+* Fix type casting to Decimal from Float with large precision.
+
+ *Tomohiro Hashidate*
+
+* Deprecate `Reflection#source_macro`
+
+ `Reflection#source_macro` is no longer needed in Active Record
+ source so it has been deprecated. Code that used `source_macro`
+ was removed in #16353.
+
+ *Eileen M. Uchtitelle*, *Aaron Patterson*
+
+* No verbose backtrace by db:drop when database does not exist.
+
+ Fixes #16295.
+
+ *Kenn Ejima*
+
+* Add support for Postgresql JSONB.
+
+ Example:
+
+ create_table :posts do |t|
+ t.jsonb :meta_data
+ end
+
+ *Philippe Creux*, *Chris Teague*
+
+* `db:purge` with MySQL respects `Rails.env`.
+
+ *Yves Senn*
+
+* `change_column_default :table, :column, nil` with PostgreSQL will issue a
+ `DROP DEFAULT` instead of a `DEFAULT NULL` query.
+
+ Fixes #16261.
+
+ *Matthew Draper*, *Yves Senn*
+
+* Allow to specify a type for the foreign key column in `references`
+ and `add_reference`.
+
+ Example:
+
+ change_table :vehicle do |t|
+ t.references :station, type: :uuid
+ end
+
+ *Andrey Novikov*, *Łukasz Sarnacki*
+
+* `create_join_table` removes a common prefix when generating the join table.
+ This matches the existing behavior of HABTM associations.
+
+ Fixes #13683.
+
+ *Stefan Kanev*
+
+* Dont swallow errors on compute_type when having a bad alias_method on
+ a class.
+
+ *arthurnn*
+
+* PostgreSQL invalid `uuid` are convert to nil.
+
+ *Abdelkader Boudih*
+
+* Restore 4.0 behavior for using serialize attributes with `JSON` as coder.
+
+ With 4.1.x, `serialize` started returning a string when `JSON` was passed as
+ the second attribute. It will now return a hash as per previous versions.
+
+ Example:
+
+ class Post < ActiveRecord::Base
+ serialize :comment, JSON
+ end
+
+ class Comment
+ include ActiveModel::Model
+ attr_accessor :category, :text
+ end
+
+ post = Post.create!
+ post.comment = Comment.new(category: "Animals", text: "This is a comment about squirrels.")
+ post.save!
+
+ # 4.0
+ post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."}
+
+ # 4.1 before
+ post.comment # => "#<Comment:0x007f80ab48ff98>"
+
+ # 4.1 after
+ post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."}
+
+ When using `JSON` as the coder in `serialize`, Active Record will use the
+ new `ActiveRecord::Coders::JSON` coder which delegates its `dump/load` to
+ `ActiveSupport::JSON.encode/decode`. This ensures special objects are dumped
+ correctly using the `#as_json` hook.
+
+ To keep the previous behaviour, supply a custom coder instead
+ ([example](https://gist.github.com/jenncoop/8c4142bbe59da77daa63)).
+
+ Fixes #15594.
+
+ *Jenn Cooper*
+
+* Do not use `RENAME INDEX` syntax for MariaDB 10.0.
+
+ Fixes #15931.
+
+ *Jeff Browning*
+
+* Calling `#empty?` on a `has_many` association would use the value from the
+ counter cache if one exists.
+
+ *David Verhasselt*
+
+* Fix the schema dump generated for tables without constraints and with
+ primary key with default value of custom PostgreSQL function result.
+
+ Fixes #16111.
+
+ *Andrey Novikov*
+
* Fix the SQL generated when a `delete_all` is run on an association to not
produce an `IN` statements.
@@ -47,7 +202,7 @@
* Move 'dependent: :destroy' handling for 'belongs_to'
from 'before_destroy' to 'after_destroy' callback chain
- Fix #12380.
+ Fixes #12380.
*Ivan Antropov*
@@ -168,14 +323,16 @@
* `ActiveRecord::Dirty` now detects in-place changes to mutable values.
Serialized attributes on ActiveRecord models will no longer save when
- unchanged. Fixes #8328.
+ unchanged.
+
+ Fixes #8328.
*Sean Griffin*
* Pluck now works when selecting columns from different tables with the same
name.
- Fixes #15649
+ Fixes #15649.
*Sean Griffin*
@@ -329,7 +486,7 @@
* Fixed the inferred table name of a has_and_belongs_to_many auxiliar
table inside a schema.
- Fixes #14824
+ Fixes #14824.
*Eric Chahin*
@@ -871,7 +1028,7 @@
*Vilius Luneckas* *Ahmed AbouElhamayed*
* `before_add` callbacks are fired before the record is saved on
- `has_and_belongs_to_many` assocations *and* on `has_many :through`
+ `has_and_belongs_to_many` associations *and* on `has_many :through`
associations. Before this change, `before_add` callbacks would be fired
before the record was saved on `has_and_belongs_to_many` associations, but
*not* on `has_many :through` associations.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 17b00bbaea..9028970a3d 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -97,6 +97,7 @@ module ActiveRecord
module Coders
autoload :YAMLColumn, 'active_record/coders/yaml_column'
+ autoload :JSON, 'active_record/coders/json'
end
module AttributeMethods
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d9b339a1f6..1c5a737696 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -717,9 +717,9 @@ module ActiveRecord
# == Eager loading of associations
#
# Eager loading is a way to find objects of a certain class and a number of named associations.
- # This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100
+ # This is one of the easiest ways of to prevent the dreaded N+1 problem in which fetching 100
# posts that each need to display their author triggers 101 database queries. Through the
- # use of eager loading, the 101 queries can be reduced to 2.
+ # use of eager loading, the number of queries will be reduced from 101 to 2.
#
# class Post < ActiveRecord::Base
# belongs_to :author
@@ -1052,7 +1052,7 @@ module ActiveRecord
# Specifies a one-to-many association. The following methods for retrieval and query of
# collections of associated objects will be added:
#
- # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
#
# [collection(force_reload = false)]
@@ -1131,7 +1131,7 @@ module ActiveRecord
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
- # The declaration can also include an options hash to specialize the behavior of the association.
+ # The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# === Options
# [:class_name]
@@ -1227,7 +1227,7 @@ module ActiveRecord
#
# The following methods for retrieval and query of a single associated object will be added:
#
- # +association+ is a placeholder for the symbol passed as the first argument, so
+ # +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
#
# [association(force_reload = false)]
@@ -1259,7 +1259,7 @@ module ActiveRecord
#
# === Options
#
- # The declaration can also include an options hash to specialize the behavior of the association.
+ # The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# Options are:
# [:class_name]
@@ -1338,7 +1338,7 @@ module ActiveRecord
# Methods will be added for retrieval and query for a single associated object, for which
# this object holds an id:
#
- # +association+ is a placeholder for the symbol passed as the first argument, so
+ # +association+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
#
# [association(force_reload = false)]
@@ -1364,7 +1364,7 @@ module ActiveRecord
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
# * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
- # The declaration can also include an options hash to specialize the behavior of the association.
+ # The declaration can also include an +options+ hash to specialize the behavior of the association.
#
# === Options
#
@@ -1418,7 +1418,7 @@ module ActiveRecord
#
# Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>.
# [:touch]
- # If true, the associated object will be touched (the updated_at/on attributes set to now)
+ # If true, the associated object will be touched (the updated_at/on attributes set to current time)
# when this record is either saved or destroyed. If you specify a symbol, that attribute
# will be updated with the current time in addition to the updated_at/on attribute.
# [:inverse_of]
@@ -1480,7 +1480,7 @@ module ActiveRecord
#
# Adds the following methods for retrieval and query:
#
- # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # +collection+ is a placeholder for the symbol passed as the +name+ argument, so
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
#
# [collection(force_reload = false)]
@@ -1541,7 +1541,7 @@ module ActiveRecord
# * <tt>Developer#projects.exists?(...)</tt>
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
- # The declaration may include an options hash to specialize the behavior of the association.
+ # The declaration may include an +options+ hash to specialize the behavior of the association.
#
# === Options
#
@@ -1587,7 +1587,7 @@ module ActiveRecord
scope = nil
end
- habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(:has_and_belongs_to_many, name, scope, options, self)
+ habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
builder = Builder::HasAndBelongsToMany.new name, self, options
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 17f36acf40..6e6dd7204c 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -3,7 +3,7 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
def valid_options
- super + [:remote, :dependent, :primary_key, :inverse_of, :required]
+ super + [:dependent, :primary_key, :inverse_of, :required]
end
def self.define_accessors(model, reflection)
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 453615ba87..79c3d2b0f5 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -41,6 +41,14 @@ module ActiveRecord
end
end
+ def empty?
+ if has_cached_counter?
+ size.zero?
+ else
+ super
+ end
+ end
+
private
# Returns the number of records in this collection.
@@ -116,7 +124,7 @@ module ActiveRecord
def inverse_updates_counter_named?(counter_name, reflection = reflection())
reflection.klass._reflections.values.any? { |inverse_reflection|
- :belongs_to == inverse_reflection.macro &&
+ inverse_reflection.belongs_to? &&
inverse_reflection.counter_cache_column == counter_name
}
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 007e3bc555..44c4436e95 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Has Many Through Association
module Associations
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 944caacab6..e6095d84dc 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
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 a0e83c0a02..c3bbdccad8 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -37,14 +37,9 @@ module ActiveRecord
table = tables.shift
klass = reflection.klass
- case reflection.source_macro
- when :belongs_to
- key = reflection.association_primary_key
- foreign_key = reflection.foreign_key
- else
- key = reflection.foreign_key
- foreign_key = reflection.active_record_primary_key
- end
+ join_keys = reflection.join_keys(klass)
+ key = join_keys.key
+ foreign_key = join_keys.foreign_key
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
@@ -95,7 +90,7 @@ module ActiveRecord
# end
#
# If I execute `Physician.joins(:appointments).to_a` then
- # reflection # => #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
+ # klass # => Physician
# table # => #<Arel::Table @name="appointments" ...>
# key # => physician_id
# foreign_table # => #<Arel::Table @name="physicians" ...>
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index f00fef8b9e..611d471e62 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module Associations
module ThroughAssociation #:nodoc:
- delegate :source_reflection, :through_reflection, :chain, :to => :reflection
+ delegate :source_reflection, :through_reflection, :to => :reflection
protected
@@ -13,7 +13,7 @@ module ActiveRecord
# 2. To get the type conditions for any STI models in the chain
def target_scope
scope = super
- chain.drop(1).each do |reflection|
+ reflection.chain.drop(1).each do |reflection|
relation = reflection.klass.all
relation.merge!(reflection.scope) if reflection.scope
@@ -77,7 +77,7 @@ module ActiveRecord
end
def ensure_mutable
- if source_reflection.macro != :belongs_to
+ unless source_reflection.belongs_to?
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e3ac891520..a2bb78dfcc 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -73,7 +73,7 @@ module ActiveRecord
# 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
+ # Use a mutex; we don't want two threads simultaneously trying to define
# attribute methods.
generated_attribute_methods.synchronize do
return false if @attribute_methods_generated
@@ -193,7 +193,7 @@ module ActiveRecord
#
# 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, ...>
+ # # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
# # => nil
@@ -251,7 +251,7 @@ module ActiveRecord
# person.has_attribute?('age') # => true
# person.has_attribute?(:nothing) # => false
def has_attribute?(attr_name)
- @attributes.include?(attr_name.to_s)
+ @attributes.key?(attr_name.to_s)
end
# Returns an array of names for the attributes available on this object.
@@ -386,7 +386,7 @@ module ActiveRecord
def attribute_method?(attr_name) # :nodoc:
# We check defined? because Syck calls respond_to? before actually calling initialize.
- defined?(@attributes) && @attributes.include?(attr_name)
+ defined?(@attributes) && @attributes.key?(attr_name)
end
private
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index e1a86fd3aa..b58295a106 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -34,7 +34,7 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
- reset_changes
+ clear_changes_information
end
end
@@ -64,7 +64,7 @@ module ActiveRecord
store_original_raw_attributes
end
- def reset_changes
+ def clear_changes_information
super
original_raw_attributes.clear
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index cadad60ddd..9bd333bbac 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -39,6 +39,12 @@ module ActiveRecord
read_attribute_before_type_cast(self.class.primary_key)
end
+ # Returns the primary key previous value.
+ def id_was
+ sync_with_transaction_state
+ attribute_was(self.class.primary_key)
+ end
+
protected
def attribute_method?(attr_name)
@@ -54,7 +60,7 @@ module ActiveRecord
end
end
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was).to_set
def dangerous_attribute_method?(method_name)
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 734d94865a..264ce2bdfa 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -37,7 +37,12 @@ module ActiveRecord
# serialize :preferences, Hash
# end
def serialize(attr_name, class_name_or_coder = Object)
- coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
+ # When ::JSON is used, force it to go through the Active Support JSON encoder
+ # to ensure special objects (e.g. Active Record models) are dumped correctly
+ # using the #as_json hook.
+ coder = if class_name_or_coder == ::JSON
+ Coders::JSON
+ elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
class_name_or_coder
else
Coders::YAMLColumn.new(class_name_or_coder)
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 64df6f6358..98ac63c7e1 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -21,8 +21,8 @@ module ActiveRecord
end
alias_method :to_h, :to_hash
- def include?(name)
- attributes.include?(name) && self[name].initialized?
+ def key?(name)
+ attributes.key?(name) && self[name].initialized?
end
def fetch_value(name, &block)
@@ -53,7 +53,7 @@ module ActiveRecord
end
def reset(key)
- if include?(key)
+ if key?(key)
write_from_database(key, nil)
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 64cc5b68cc..f978fbd0a4 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -141,6 +141,7 @@ module ActiveRecord #:nodoc:
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
# Query methods allow you to test whether an attribute value is present.
+ # For numeric values, present is defined as non-zero.
#
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
# to determine whether the user has a name:
diff --git a/activerecord/lib/active_record/coders/json.rb b/activerecord/lib/active_record/coders/json.rb
new file mode 100644
index 0000000000..75d3bfe625
--- /dev/null
+++ b/activerecord/lib/active_record/coders/json.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module Coders # :nodoc:
+ class JSON # :nodoc:
+ def self.dump(obj)
+ ActiveSupport::JSON.encode(obj)
+ end
+
+ def self.load(json)
+ ActiveSupport::JSON.decode(json) unless json.nil?
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index cb75070e3a..a5fa9d6adc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -462,23 +462,44 @@ module ActiveRecord
#
# For example, suppose that you have 5 models, with the following hierarchy:
#
- # |
- # +-- Book
- # | |
- # | +-- ScaryBook
- # | +-- GoodBook
- # +-- Author
- # +-- BankAccount
+ # class Author < ActiveRecord::Base
+ # end
#
- # Suppose that Book is to connect to a separate database (i.e. one other
- # than the default database). Then Book, ScaryBook and GoodBook will all use
- # the same connection pool. Likewise, Author and BankAccount will use the
- # same connection pool. However, the connection pool used by Author/BankAccount
- # is not the same as the one used by Book/ScaryBook/GoodBook.
+ # class BankAccount < ActiveRecord::Base
+ # end
#
- # Normally there is only a single ConnectionHandler instance, accessible via
- # ActiveRecord::Base.connection_handler. Active Record models use this to
- # determine the connection pool that they should use.
+ # class Book < ActiveRecord::Base
+ # establish_connection "library_db"
+ # end
+ #
+ # class ScaryBook < Book
+ # end
+ #
+ # class GoodBook < Book
+ # end
+ #
+ # And a database.yml that looked like this:
+ #
+ # development:
+ # database: my_application
+ # host: localhost
+ #
+ # library_db:
+ # database: library
+ # host: some.library.org
+ #
+ # Your primary database in the development environment is "my_application"
+ # but the Book model connects to a separate database called "library_db"
+ # (this can even be a database on a different machine).
+ #
+ # Book, ScaryBook and GoodBook will all use the same connection pool to
+ # "library_db" while Author, BankAccount, and any other models you create
+ # will use the default connection pool to "my_application".
+ #
+ # The various connection pools are managed by a single instance of
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
+ # All Active Record models use this handler to determine the connection pool that they
+ # should use.
class ConnectionHandler
def initialize
# These caches are keyed by klass.name, NOT klass. Keying them by klass
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 e8ce00d92b..98e96099cb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -203,62 +203,30 @@ module ActiveRecord
if options[:isolation]
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
end
-
yield
else
- within_new_transaction(options) { yield }
+ transaction_manager.within_new_transaction(options) { yield }
end
rescue ActiveRecord::Rollback
# rollbacks are silently swallowed
end
- def within_new_transaction(options = {}) #:nodoc:
- transaction = begin_transaction(options)
- yield
- rescue Exception => error
- rollback_transaction if transaction
- raise
- ensure
- begin
- commit_transaction unless error
- rescue Exception
- rollback_transaction
- raise
- end
- end
-
- def open_transactions
- @transaction.number
- end
+ attr_reader :transaction_manager #:nodoc:
- def current_transaction #:nodoc:
- @transaction
- end
+ delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
def transaction_open?
- @transaction.open?
- end
-
- def begin_transaction(options = {}) #:nodoc:
- @transaction = @transaction.begin(options)
- end
-
- def commit_transaction #:nodoc:
- @transaction = @transaction.commit
- end
-
- def rollback_transaction #:nodoc:
- @transaction = @transaction.rollback
+ current_transaction.open?
end
def reset_transaction #:nodoc:
- @transaction = ClosedTransaction.new(self)
+ @transaction_manager = TransactionManager.new(self)
end
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
# can be called.
def add_transaction_record(record)
- @transaction.add_record(record)
+ current_transaction.add_record(record)
end
# Begins the transaction (and turns off auto-committing).
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 98e6795f10..e44ccb7d81 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -280,12 +280,22 @@ module ActiveRecord
column(:updated_at, :datetime, options)
end
+ # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
+ # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
+ # by default, the <tt>:type</tt> option can be used to specify a different type.
+ #
+ # t.references(:user)
+ # t.references(:user, type: "string")
+ # t.belongs_to(:supplier, polymorphic: true)
+ #
+ # See SchemaStatements#add_reference
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
index_options = options.delete(:index)
+ type = options.delete(:type) || :integer
args.each do |col|
- column("#{col}_id", :integer, options)
+ column("#{col}_id", type, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
@@ -500,11 +510,14 @@ module ActiveRecord
end
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
- # <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
+ # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. The reference column will be an +integer+
+ # by default, the <tt>:type</tt> option can be used to specify a different type.
#
# t.references(:user)
+ # t.references(:user, type: "string")
# t.belongs_to(:supplier, polymorphic: true)
#
+ # See SchemaStatements#add_reference
def references(*args)
options = args.extract_options!
args.each do |ref_name|
@@ -519,6 +532,7 @@ module ActiveRecord
# t.remove_references(:user)
# t.remove_belongs_to(:supplier, polymorphic: true)
#
+ # See SchemaStatements#remove_reference
def remove_references(*args)
options = args.extract_options!
args.each do |ref_name|
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 5814c2b711..10753defc2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -602,12 +602,18 @@ module ActiveRecord
end
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
+ # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
+ # a different type.
# <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
#
- # ====== Create a user_id column
+ # ====== Create a user_id integer column
#
# add_reference(:products, :user)
#
+ # ====== Create a user_id string column
+ #
+ # add_reference(:products, :user, type: :string)
+ #
# ====== Create a supplier_id and supplier_type columns
#
# add_belongs_to(:products, :supplier, polymorphic: true)
@@ -619,7 +625,8 @@ module ActiveRecord
def add_reference(table_name, ref_name, options = {})
polymorphic = options.delete(:polymorphic)
index_options = options.delete(:index)
- add_column(table_name, "#{ref_name}_id", :integer, options)
+ type = options.delete(:type) || :integer
+ add_column(table_name, "#{ref_name}_id", type, options)
add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index bc4884b538..4a7f2aaca8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -1,20 +1,7 @@
module ActiveRecord
module ConnectionAdapters
- class Transaction #:nodoc:
- attr_reader :connection
-
- def initialize(connection)
- @connection = connection
- @state = TransactionState.new
- end
-
- def state
- @state
- end
- end
-
class TransactionState
- attr_accessor :parent
+ attr_reader :parent
VALID_STATES = Set.new([:committed, :rolledback, nil])
@@ -43,82 +30,24 @@ module ActiveRecord
end
end
- class ClosedTransaction < Transaction #:nodoc:
- def number
- 0
- end
-
- def begin(options = {})
- RealTransaction.new(connection, self, options)
- end
-
- def closed?
- true
- end
-
- def open?
- false
- end
-
- def joinable?
- false
- end
-
- # This is a noop when there are no open transactions
- def add_record(record)
- end
+ class NullTransaction #:nodoc:
+ def initialize; end
+ def closed?; true; end
+ def open?; false; end
+ def joinable?; false; end
+ def add_record(record); end
end
- class OpenTransaction < Transaction #:nodoc:
- attr_reader :parent, :records
- attr_writer :joinable
-
- def initialize(connection, parent, options = {})
- super connection
-
- @parent = parent
- @records = []
- @finishing = false
- @joinable = options.fetch(:joinable, true)
- end
-
- # This state is necessary so that we correctly handle stuff that might
- # happen in a commit/rollback. But it's kinda distasteful. Maybe we can
- # find a better way to structure it in the future.
- def finishing?
- @finishing
- end
-
- def joinable?
- @joinable && !finishing?
- end
-
- def number
- if finishing?
- parent.number
- else
- parent.number + 1
- end
- end
-
- def begin(options = {})
- if finishing?
- parent.begin
- else
- SavepointTransaction.new(connection, self, options)
- end
- end
+ class Transaction #:nodoc:
- def rollback
- @finishing = true
- perform_rollback
- parent
- end
+ attr_reader :connection, :state, :records, :savepoint_name
+ attr_writer :joinable
- def commit
- @finishing = true
- perform_commit
- parent
+ def initialize(connection, options)
+ @connection = connection
+ @state = TransactionState.new
+ @records = []
+ @joinable = options.fetch(:joinable, true)
end
def add_record(record)
@@ -129,19 +58,25 @@ module ActiveRecord
end
end
- def rollback_records
+ def rollback
@state.set_state(:rolledback)
+ end
+
+ def rollback_records
records.uniq.each do |record|
begin
- record.rolledback!(parent.closed?)
+ record.rolledback! full_rollback?
rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
- def commit_records
+ def commit
@state.set_state(:committed)
+ end
+
+ def commit_records
records.uniq.each do |record|
begin
record.committed!
@@ -151,19 +86,40 @@ module ActiveRecord
end
end
- def closed?
- false
+ def full_rollback?; true; end
+ def joinable?; @joinable; end
+ def closed?; false; end
+ def open?; !closed?; end
+ end
+
+ class SavepointTransaction < Transaction
+
+ def initialize(connection, savepoint_name, options)
+ super(connection, options)
+ if options[:isolation]
+ raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
+ end
+ connection.create_savepoint(@savepoint_name = savepoint_name)
+ end
+
+ def rollback
+ super
+ connection.rollback_to_savepoint(savepoint_name)
+ rollback_records
end
- def open?
- true
+ def commit
+ super
+ connection.release_savepoint(savepoint_name)
end
+
+ def full_rollback?; false; end
end
- class RealTransaction < OpenTransaction #:nodoc:
- def initialize(connection, parent, options = {})
- super
+ class RealTransaction < Transaction
+ def initialize(connection, options)
+ super
if options[:isolation]
connection.begin_isolated_db_transaction(options[:isolation])
else
@@ -171,37 +127,71 @@ module ActiveRecord
end
end
- def perform_rollback
+ def rollback
+ super
connection.rollback_db_transaction
rollback_records
end
- def perform_commit
+ def commit
+ super
connection.commit_db_transaction
commit_records
end
end
- class SavepointTransaction < OpenTransaction #:nodoc:
- def initialize(connection, parent, options = {})
- if options[:isolation]
- raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
- end
+ class TransactionManager #:nodoc:
+ def initialize(connection)
+ @stack = []
+ @connection = connection
+ end
- super
- connection.create_savepoint
+ def begin_transaction(options = {})
+ transaction =
+ if @stack.empty?
+ RealTransaction.new(@connection, options)
+ else
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
+ end
+ @stack.push(transaction)
+ transaction
end
- def perform_rollback
- connection.rollback_to_savepoint
- rollback_records
+ def commit_transaction
+ @stack.pop.commit
end
- def perform_commit
- @state.set_state(:committed)
- @state.parent = parent.state
- connection.release_savepoint
+ def rollback_transaction
+ @stack.pop.rollback
+ end
+
+ def within_new_transaction(options = {})
+ transaction = begin_transaction options
+ yield
+ rescue Exception => error
+ transaction.rollback if transaction
+ raise
+ ensure
+ begin
+ transaction.commit unless error
+ rescue Exception
+ transaction.rollback
+ raise
+ ensure
+ @stack.pop if transaction
+ end
end
+
+ def open_transactions
+ @stack.size
+ end
+
+ def current_transaction
+ @stack.last || NULL_TRANSACTION
+ end
+
+ private
+ NULL_TRANSACTION = NullTransaction.new
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index f8c054eb69..a1b6671664 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -45,7 +45,8 @@ module ActiveRecord
end
autoload_at 'active_record/connection_adapters/abstract/transaction' do
- autoload :ClosedTransaction
+ autoload :TransactionManager
+ autoload :NullTransaction
autoload :RealTransaction
autoload :SavepointTransaction
autoload :TransactionState
@@ -357,7 +358,7 @@ module ActiveRecord
end
def current_savepoint_name
- "active_record_#{open_transactions}"
+ current_transaction.savepoint_name
end
# Check the connection back in to the connection pool
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 ccb957d2c8..e5417a9556 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -473,7 +473,7 @@ module ActiveRecord
end
def rename_index(table_name, old_name, new_name)
- if (version[0] == 5 && version[1] >= 7) || version[0] >= 6
+ if supports_rename_index?
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
else
super
@@ -774,10 +774,22 @@ module ActiveRecord
private
+ def version
+ @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ end
+
+ def mariadb?
+ full_version =~ /mariadb/i
+ end
+
def supports_views?
version[0] >= 5
end
+ def supports_rename_index?
+ mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
+ end
+
def configure_connection
variables = @config.fetch(:variables, {}).stringify_keys
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 1f1e2c46f4..5f9cc6edd0 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -16,7 +16,7 @@ module ActiveRecord
attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
delegate :type, :precision, :scale, :limit, :klass, :accessor,
- :text?, :number?, :binary?, :changed?,
+ :number?, :binary?, :changed?,
:type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
:type_cast_for_schema,
to: :cast_type
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 2fcb085ab2..d28a54b8f9 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -32,7 +32,7 @@ module ActiveRecord
# }
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
- @uri = URI.parse(url)
+ @uri = uri_parser.parse(url)
@adapter = @uri.scheme.gsub('-', '_')
@adapter = "postgresql" if @adapter == "postgres"
@@ -160,7 +160,7 @@ module ActiveRecord
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
# spec = Resolver.new(config).spec(:production)
# spec.adapter_method
- # # => "sqlite3"
+ # # => "sqlite3_connection"
# spec.config
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
#
@@ -250,7 +250,7 @@ module ActiveRecord
# Connection details inside of the "url" key win any merge conflicts
def resolve_hash_connection(spec)
if spec["url"] && spec["url"] !~ /^jdbc:/
- connection_hash = resolve_string_connection(spec.delete("url"))
+ connection_hash = resolve_url_connection(spec.delete("url"))
spec.merge!(connection_hash)
end
spec
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 0a14cdfe89..39d52e6349 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -269,8 +269,8 @@ module ActiveRecord
super
end
- def version
- @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ def full_version
+ @full_version ||= @connection.info[:version]
end
def set_field_encoding field_name
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index ad07a46e51..a03bc28744 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -470,9 +470,9 @@ module ActiveRecord
rows
end
- # Returns the version of the connected MySQL server.
- def version
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ # Returns the full version of the connected MySQL server.
+ def full_version
+ @full_version ||= @connection.server_info
end
def set_field_encoding field_name
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index d05ce61330..d28a2b4fa0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -14,6 +14,7 @@ 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/jsonb'
require 'active_record/connection_adapters/postgresql/oid/money'
require 'active_record/connection_adapters/postgresql/oid/point'
require 'active_record/connection_adapters/postgresql/oid/range'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
index 243ecd13cf..1dbb40ca1d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
@@ -19,6 +19,32 @@ module ActiveRecord
value
end
end
+
+ def type_cast_for_database(value)
+ Data.new(super) if value
+ end
+
+ class Data
+ def initialize(value)
+ @value = value
+ end
+
+ def to_s
+ value
+ end
+
+ def binary?
+ /\A[01]*\Z/ === value
+ end
+
+ def hex?
+ /\A[0-9A-F]*\Z/i === value
+ end
+
+ protected
+
+ attr_reader :value
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
new file mode 100644
index 0000000000..34ed32ad35
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb
@@ -0,0 +1,23 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Jsonb < Json # :nodoc:
+ def type
+ :jsonb
+ end
+
+ def changed_in_place?(raw_old_value, new_value)
+ # Postgres does not preserve insignificant whitespaces when
+ # roundtripping jsonb columns. This causes some false positives for
+ # the comparison here. Therefore, we need to parse and re-dump the
+ # raw value here to ensure the insignificant whitespaces are
+ # consitent with our encoder's output.
+ raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value))
+ super(raw_old_value, new_value)
+ 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
index 89728b0fe2..dd97393eac 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb
@@ -3,12 +3,21 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Uuid < Type::Value # :nodoc:
+ RFC_4122 = %r{\A\{?[a-fA-F0-9]{4}-?
+ [a-fA-F0-9]{4}-?
+ [a-fA-F0-9]{4}-?
+ [1-5][a-fA-F0-9]{3}-?
+ [8-Bab][a-fA-F0-9]{3}-?
+ [a-fA-F0-9]{4}-?
+ [a-fA-F0-9]{4}-?
+ [a-fA-F0-9]{4}-?\}?\z}x
+
def type
:uuid
end
def type_cast(value)
- value.presence
+ value.to_s[RFC_4122, 0]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
index 7323f12763..334af7c598 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/xml.rb
@@ -7,10 +7,6 @@ module ActiveRecord
:xml
end
- def text?
- false
- end
-
def type_cast_for_database(value)
return unless value
Data.new(super)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 5359c5b666..cf5c8d288e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -18,8 +18,6 @@ module ActiveRecord
def quote(value, column = nil) #:nodoc:
return super unless column
- sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale)
-
case value
when Float
if value.infinite? || value.nan?
@@ -27,16 +25,6 @@ module ActiveRecord
else
super
end
- when String
- case sql_type
- when /^bit/
- case value
- when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation
- when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation
- end
- else
- super
- end
else
super
end
@@ -100,6 +88,12 @@ module ActiveRecord
"'#{escape_bytea(value.to_s)}'"
when OID::Xml::Data
"xml '#{quote_string(value.to_s)}'"
+ when OID::Bit::Data
+ if value.binary?
+ "B'#{value}'"
+ elsif value.hex?
+ "X'#{value}'"
+ end
else
super
end
@@ -112,7 +106,7 @@ module ActiveRecord
# See http://deveiate.org/code/pg/PGconn.html#method-i-exec_prepared-doc
# for more information
{ value: value.to_s, format: 1 }
- when OID::Xml::Data
+ when OID::Xml::Data, OID::Bit::Data
value.to_s
else
super
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 0867e5ef54..83554bbf74 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -64,6 +64,10 @@ module ActiveRecord
column(name, :json, options)
end
+ def jsonb(name, options = {})
+ column(name, :jsonb, options)
+ end
+
def citext(name, options = {})
column(name, :citext, options)
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 e09672d239..7042817672 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -423,8 +423,16 @@ module ActiveRecord
def change_column_default(table_name, column_name, default)
clear_cache!
column = column_for(table_name, column_name)
+ return unless column
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column
+ alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
+ if default.nil?
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
+ execute alter_column_query % "DROP DEFAULT"
+ else
+ execute alter_column_query % "SET DEFAULT #{quote_default_value(default, column)}"
+ end
end
def change_column_null(table_name, column_name, null, default = nil)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index f660fc41cf..eede374678 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -451,6 +451,7 @@ module ActiveRecord
m.register_type 'point', OID::Point.new
m.register_type 'hstore', OID::Hstore.new
m.register_type 'json', OID::Json.new
+ m.register_type 'jsonb', OID::Jsonb.new
m.register_type 'cidr', OID::Cidr.new
m.register_type 'inet', OID::Inet.new
m.register_type 'uuid', OID::Uuid.new
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 4d8afcf16a..a10ce330c7 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module ConnectionAdapters
class SchemaCache
@@ -20,6 +19,7 @@ module ActiveRecord
# A cached lookup for table existence.
def table_exists?(name)
+ prepare_tables if @tables.empty?
return @tables[name] if @tables.key? name
@tables[name] = connection.table_exists?(name)
@@ -83,6 +83,12 @@ module ActiveRecord
def marshal_load(array)
@version, @columns, @columns_hash, @primary_keys, @tables = array
end
+
+ private
+
+ def prepare_tables
+ connection.tables.each { |table| @tables[table] = true }
+ 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 32fb51d31a..faf1cdc686 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -50,6 +50,16 @@ module ActiveRecord
end
end
+ class SQLite3String < Type::String # :nodoc:
+ def type_cast_for_database(value)
+ if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT
+ value.encode(Encoding::UTF_8)
+ else
+ super
+ end
+ end
+ end
+
# The SQLite3 adapter works SQLite 3.6.16 or newer
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
#
@@ -220,13 +230,23 @@ module ActiveRecord
# QUOTING ==================================================
def _quote(value) # :nodoc:
- if value.is_a?(Type::Binary::Data)
+ case value
+ when Type::Binary::Data
"x'#{value.hex}'"
else
super
end
end
+ def _type_cast(value) # :nodoc:
+ case value
+ when BigDecimal
+ value.to_f
+ else
+ super
+ end
+ end
+
def quote_string(s) #:nodoc:
@connection.class.quote(s)
end
@@ -249,19 +269,6 @@ module ActiveRecord
end
end
- def type_cast(value, column) # :nodoc:
- return value.to_f if BigDecimal === value
- return super unless String === value
- return super unless column && value
-
- value = super
- if column.type == :string && value.encoding == Encoding::ASCII_8BIT
- logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
- value = value.encode Encoding::UTF_8
- end
- value
- end
-
# DATABASE STATEMENTS ======================================
def explain(arel, binds = [])
@@ -378,7 +385,7 @@ module ActiveRecord
table_name && tables(nil, table_name).any?
end
- # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name) #:nodoc:
table_structure(table_name).map do |field|
case field["dflt_value"]
@@ -503,6 +510,7 @@ module ActiveRecord
def initialize_type_map(m)
super
m.register_type(/binary/i, SQLite3Binary.new)
+ register_class_with_limit m, %r(char)i, SQLite3String
end
def select(sql, name = nil, binds = []) #:nodoc:
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index b11c4f804f..d22806fbdf 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -107,6 +107,11 @@ module ActiveRecord
end
module ClassMethods
+ def allocate
+ define_attribute_methods
+ super
+ end
+
def initialize_find_by_cache
self.find_by_statement_cache = {}.extend(Mutex_m)
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index a33c7c64a7..f0b6afc4b4 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -34,7 +34,7 @@ module ActiveRecord
foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
- reflection = child_class._reflections.values.find { |e| :belongs_to == e.macro && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
+ reflection = child_class._reflections.values.find { |e| e.belongs_to? && 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({
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 38c6dcf88d..f0ee433d0b 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -69,12 +69,12 @@ module ActiveRecord
#
# Where conditions on an enum attribute must use the ordinal value of an enum.
module Enum
- def self.extended(base)
+ def self.extended(base) # :nodoc:
base.class_attribute(:defined_enums)
base.defined_enums = {}
end
- def inherited(base)
+ def inherited(base) # :nodoc:
base.defined_enums = defined_enums.deep_dup
super
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index e65dab07ba..727a9befc1 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -27,7 +27,7 @@ module ActiveRecord
end.join("\n")
end.join("\n")
- # Overriding inspect to be more human readable, specially in the console.
+ # Overriding inspect to be more human readable, especially in the console.
def str.inspect
self
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 4cd5f92207..4306b36ae1 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -2,7 +2,7 @@ require 'erb'
require 'yaml'
require 'zlib'
require 'active_support/dependencies'
-require 'active_support/core_ext/securerandom'
+require 'active_support/core_ext/digest/uuid'
require 'active_record/fixture_set/file'
require 'active_record/errors'
@@ -551,7 +551,7 @@ module ActiveRecord
# Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
def self.identify(label, column_type = :integer)
if column_type == :uuid
- SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, label.to_s)
+ Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
else
Zlib.crc32(label.to_s) % MAX_ID
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index f6c265a6d6..251d682a02 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -151,14 +151,8 @@ module ActiveRecord
candidates << type_name
candidates.each do |candidate|
- begin
- constant = ActiveSupport::Dependencies.constantize(candidate)
- return constant if candidate == constant.to_s
- # We don't want to swallow NoMethodError < NameError errors
- rescue NoMethodError
- raise
- rescue NameError
- end
+ constant = ActiveSupport::Dependencies.safe_constantize(candidate)
+ return constant if candidate == constant.to_s
end
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index e94b6ae9eb..a6847e28c2 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -399,7 +399,7 @@ module ActiveRecord
def load_schema_if_pending!
if ActiveRecord::Migrator.needs_migration?
- ActiveRecord::Tasks::DatabaseTasks.load_schema
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current
check_pending!
end
end
@@ -814,22 +814,22 @@ module ActiveRecord
migrations = migrations(migrations_paths)
migrations.select! { |m| yield m } if block_given?
- self.new(:up, migrations, target_version).migrate
+ new(:up, migrations, target_version).migrate
end
def down(migrations_paths, target_version = nil, &block)
migrations = migrations(migrations_paths)
migrations.select! { |m| yield m } if block_given?
- self.new(:down, migrations, target_version).migrate
+ new(:down, migrations, target_version).migrate
end
def run(direction, migrations_paths, target_version)
- self.new(direction, migrations(migrations_paths), target_version).run
+ new(direction, migrations(migrations_paths), target_version).run
end
def open(migrations_paths)
- self.new(:up, migrations(migrations_paths), nil)
+ new(:up, migrations(migrations_paths), nil)
end
def schema_migrations_table_name
@@ -892,7 +892,7 @@ module ActiveRecord
private
def move(direction, migrations_paths, steps)
- migrator = self.new(direction, migrations(migrations_paths))
+ migrator = new(direction, migrations(migrations_paths))
start_index = migrator.migrations.index(migrator.current_migration)
if start_index
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index f833caaab6..36256415df 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -87,7 +87,7 @@ module ActiveRecord
alias :add_belongs_to :add_reference
alias :remove_belongs_to :remove_reference
- def change_table(table_name, options = {})
+ def change_table(table_name, options = {}) # :nodoc:
yield delegate.update_table_definition(table_name, self)
end
diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb
index ebf64cbcdc..05569fadbd 100644
--- a/activerecord/lib/active_record/migration/join_table.rb
+++ b/activerecord/lib/active_record/migration/join_table.rb
@@ -8,7 +8,7 @@ module ActiveRecord
end
def join_table_name(table_1, table_2)
- [table_1.to_s, table_2.to_s].sort.join("_").to_sym
+ ModelSchema.derive_join_table_name(table_1, table_2).to_sym
end
end
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 092c3b4fb7..850220babd 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -55,6 +55,17 @@ module ActiveRecord
delegate :type_for_attribute, to: :class
end
+ # Derives the join table name for +first_table+ and +second_table+. The
+ # table names appear in alphabetical order. A common prefix is removed
+ # (useful for namespaced models like Music::Artist and Music::Record):
+ #
+ # artists, records => artists_records
+ # records, artists => artists_records
+ # music_artists, music_records => music_artists_records
+ def self.derive_join_table_name(first_table, second_table) # :nodoc:
+ [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
+ end
+
module ClassMethods
# Guesses the table name (in forced lower-case) based on the name of the class in the
# inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 96e44c2f59..51b1931ed5 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -36,6 +36,23 @@ module ActiveRecord
end
end
+ # Creates an object (or multiple objects) and saves it to the database,
+ # if validations pass. Raises a RecordInvalid error if validations fail,
+ # unlike Base#create.
+ #
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes.
+ # These describe which attributes to be created on the object, or
+ # multiple objects when given an Array of Hashes.
+ def create!(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create!(attr, &block) }
+ else
+ object = new(attributes, &block)
+ object.save!
+ object
+ end
+ end
+
# Given an attributes hash, +instantiate+ returns a new instance of
# the appropriate class. Accepts only keys as strings.
#
@@ -270,7 +287,8 @@ module ActiveRecord
# This method raises an +ActiveRecord::ActiveRecordError+ when called on new
# objects, or when at least one of the attributes is marked as readonly.
def update_columns(attributes)
- raise ActiveRecordError, "cannot update on a new record object" unless persisted?
+ raise ActiveRecordError, "cannot update a new record" if new_record?
+ raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
attributes.each_key do |key|
verify_readonly_attribute(key.to_s)
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 16ad942912..dcb2bd3d84 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Query Cache
class QueryCache
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index fa94df7a52..458862a538 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -41,10 +41,7 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
task :migrate => [:environment, :load_config] do
- ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
- ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
- ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
- end
+ ActiveRecord::Tasks::DatabaseTasks.migrate
db_namespace['_dump'].invoke if ActiveRecord::Base.dump_schema_after_migration
end
@@ -243,7 +240,7 @@ db_namespace = namespace :db do
desc 'Load a schema.rb file into the database'
task :load => [:environment, :load_config] do
- ActiveRecord::Tasks::DatabaseTasks.load_schema(:ruby, ENV['SCHEMA'])
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
task :load_if_ruby => ['db:create', :environment] do
@@ -289,7 +286,7 @@ db_namespace = namespace :db do
desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
- ActiveRecord::Tasks::DatabaseTasks.load_schema(:sql, ENV['DB_STRUCTURE'])
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['DB_STRUCTURE'])
end
task :load_if_sql => ['db:create', :environment] do
@@ -320,9 +317,8 @@ db_namespace = namespace :db do
task :load_schema => %w(db:test:deprecated db:test:purge) do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
ActiveRecord::Schema.verbose = false
- db_namespace["schema:load"].invoke
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :ruby, ENV['SCHEMA']
ensure
if should_reconnect
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
@@ -332,12 +328,7 @@ db_namespace = namespace :db do
# desc "Recreate the test database from an existent structure.sql file"
task :load_structure => %w(db:test:deprecated db:test:purge) do
- begin
- ActiveRecord::Tasks::DatabaseTasks.current_config(:config => ActiveRecord::Base.configurations['test'])
- db_namespace["structure:load"].invoke
- ensure
- ActiveRecord::Tasks::DatabaseTasks.current_config(:config => nil)
- end
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA']
end
# desc "Recreate the test database from a fresh schema"
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index b3ddfd63d4..85bbac43e4 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module ReadonlyAttributes
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 28c39bdd5c..1547c8e3f4 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -13,14 +13,21 @@ module ActiveRecord
end
def self.create(macro, name, scope, options, ar)
- case macro
- when :has_many, :belongs_to, :has_one
- klass = options[:through] ? ThroughReflection : AssociationReflection
- when :composed_of
- klass = AggregateReflection
- end
-
- klass.new(macro, name, scope, options, ar)
+ klass = case macro
+ when :composed_of
+ AggregateReflection
+ when :has_many
+ HasManyReflection
+ when :has_one
+ HasOneReflection
+ when :belongs_to
+ BelongsToReflection
+ else
+ raise "Unsupported Macro: #{macro}"
+ end
+
+ reflection = klass.new(name, scope, options, ar)
+ options[:through] ? ThroughReflection.new(reflection) : reflection
end
def self.add_reflection(ar, name, reflection)
@@ -31,7 +38,7 @@ module ActiveRecord
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
end
- # \Reflection enables to interrogate Active Record classes and objects
+ # \Reflection enables interrogating Active Record classes and objects
# about their associations and aggregations. This information can,
# for example, be used in a form builder that takes an Active Record object
# and creates input fields for all of the attributes depending on their type
@@ -110,26 +117,64 @@ module ActiveRecord
end
end
+ # Holds all the methods that are shared between MacroReflection, AssociationReflection
+ # and ThroughReflection
+ class AbstractReflection # :nodoc:
+ def table_name
+ klass.table_name
+ end
+
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
+ # be passed to the class's constructor.
+ def build_association(attributes, &block)
+ klass.new(attributes, &block)
+ end
+
+ def quoted_table_name
+ klass.quoted_table_name
+ end
+
+ def primary_key_type
+ klass.type_for_attribute(klass.primary_key)
+ end
+
+ # Returns the class name for the macro.
+ #
+ # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
+ def class_name
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
+ end
+
+ JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
+
+ def join_keys(assoc_klass)
+ JoinKeys.new(foreign_key, active_record_primary_key)
+ end
+
+ def source_macro
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.source_macro is deprecated and " \
+ "will be removed without replacement.")
+ macro
+ end
+ end
# Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
#
# MacroReflection
- # AggregateReflection
# AssociationReflection
- # ThroughReflection
- class MacroReflection
+ # AggregateReflection
+ # HasManyReflection
+ # HasOneReflection
+ # BelongsToReflection
+ # ThroughReflection
+ class MacroReflection < AbstractReflection
# Returns the name of the macro.
#
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
attr_reader :name
- # Returns the macro type.
- #
- # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:composed_of</tt>
- # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
- attr_reader :macro
-
attr_reader :scope
# Returns the hash of options used for the macro.
@@ -142,8 +187,7 @@ module ActiveRecord
attr_reader :plural_name # :nodoc:
- def initialize(macro, name, scope, options, active_record)
- @macro = macro
+ def initialize(name, scope, options, active_record)
@name = name
@scope = scope
@options = options
@@ -167,15 +211,11 @@ module ActiveRecord
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
# <tt>has_many :clients</tt> returns the Client class
def klass
- @klass ||= class_name.constantize
+ @klass ||= compute_class(class_name)
end
- # Returns the class name for the macro.
- #
- # <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
- # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
- def class_name
- @class_name ||= (options[:class_name] || derive_class_name).to_s
+ def compute_class(name)
+ name.constantize
end
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -188,23 +228,6 @@ 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
@@ -237,15 +260,18 @@ module ActiveRecord
# a new association object. Use +build_association+ or +create_association+
# instead. This allows plugins to hook into association object creation.
def klass
- @klass ||= active_record.send(:compute_type, class_name)
+ @klass ||= compute_class(class_name)
+ end
+
+ def compute_class(name)
+ active_record.send(:compute_type, name)
end
attr_reader :type, :foreign_type
attr_accessor :parent_reflection # [:name, Reflection]
- def initialize(macro, name, scope, options, active_record)
+ def initialize(name, scope, options, active_record)
super
- @collection = macro == :has_many
@automatic_inverse_of = nil
@type = options[:as] && "#{options[:as]}_type"
@foreign_type = options[:foreign_type] || "#{name}_type"
@@ -264,24 +290,10 @@ module ActiveRecord
}
end
- # Returns a new, unsaved instance of the associated class. +attributes+ will
- # be passed to the class's constructor.
- def build_association(attributes, &block)
- klass.new(attributes, &block)
- end
-
def constructable? # :nodoc:
@constructable
end
- def table_name
- klass.table_name
- end
-
- def quoted_table_name
- klass.quoted_table_name
- end
-
def join_table
@join_table ||= options[:join_table] || derive_join_table
end
@@ -290,10 +302,6 @@ module ActiveRecord
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
- def primary_key_type
- klass.type_for_attribute(klass.primary_key)
- end
-
def association_foreign_key
@association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key
end
@@ -341,9 +349,8 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
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]
+ def join_id_for(owner) # :nodoc:
+ owner[active_record_primary_key]
end
def through_reflection
@@ -370,8 +377,6 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
scope ? [[scope]] : [[]]
end
- alias :source_macro :macro
-
def has_inverse?
inverse_name
end
@@ -392,11 +397,16 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
end
+ # Returns the macro type.
+ #
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
+ def macro; raise NotImplementedError; end
+
# Returns whether or not this association reflection is for a collection
# association. Returns +true+ if the +macro+ is either +has_many+ or
# +has_and_belongs_to_many+, +false+ otherwise.
def collection?
- @collection
+ false
end
# Returns whether or not the association should be validated as part of
@@ -413,14 +423,10 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
# Returns +true+ if +self+ is a +belongs_to+ reflection.
- def belongs_to?
- macro == :belongs_to
- end
+ def belongs_to?; false; end
# Returns +true+ if +self+ is a +has_one+ reflection.
- def has_one?
- macro == :has_one
- end
+ def has_one?; false; end
def association_class
case macro
@@ -552,7 +558,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
def derive_join_table
- [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
+ ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
end
def primary_key(klass)
@@ -560,22 +566,72 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
end
- class HasAndBelongsToManyReflection < AssociationReflection #:nodoc:
- def initialize(macro, name, scope, options, active_record)
+ class HasManyReflection < AssociationReflection # :nodoc:
+ def initialize(name, scope, options, active_record)
+ super(name, scope, options, active_record)
+ end
+
+ def macro; :has_many; end
+
+ def collection?; true; end
+ end
+
+ class HasOneReflection < AssociationReflection # :nodoc:
+ def initialize(name, scope, options, active_record)
+ super(name, scope, options, active_record)
+ end
+
+ def macro; :has_one; end
+
+ def has_one?; true; end
+ end
+
+ class BelongsToReflection < AssociationReflection # :nodoc:
+ def initialize(name, scope, options, active_record)
+ super(name, scope, options, active_record)
+ end
+
+ def macro; :belongs_to; end
+
+ def belongs_to?; true; end
+
+ def join_keys(assoc_klass)
+ key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
+ JoinKeys.new(key, foreign_key)
+ end
+
+ def join_id_for(owner) # :nodoc:
+ owner[foreign_key]
+ end
+ end
+
+ class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
+ def initialize(name, scope, options, active_record)
super
- @collection = true
+ end
+
+ def macro; :has_and_belongs_to_many; end
+
+ def 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:
+ class ThroughReflection < AbstractReflection #:nodoc:
+ attr_reader :delegate_reflection
delegate :foreign_key, :foreign_type, :association_foreign_key,
:active_record_primary_key, :type, :to => :source_reflection
- def initialize(macro, name, scope, options, active_record)
- super
- @source_reflection_name = options[:source]
+ def initialize(delegate_reflection)
+ @delegate_reflection = delegate_reflection
+ @klass = delegate_reflection.options[:class]
+ @source_reflection_name = delegate_reflection.options[:source]
+ end
+
+ def klass
+ @klass ||= delegate_reflection.compute_class(class_name)
end
# Returns the source of the through reflection. It checks both a singularized
@@ -593,7 +649,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
#
# tags_reflection = Post.reflect_on_association(:tags)
# tags_reflection.source_reflection
- # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
+ # # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
#
def source_reflection
through_reflection.klass._reflect_on_association(source_reflection_name)
@@ -609,7 +665,7 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
#
# tags_reflection = Post.reflect_on_association(:tags)
# tags_reflection.through_reflection
- # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
+ # # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
#
def through_reflection
active_record._reflect_on_association(options[:through])
@@ -629,8 +685,8 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
#
# tags_reflection = Post.reflect_on_association(:tags)
# tags_reflection.chain
- # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
- # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
+ # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
#
def chain
@chain ||= begin
@@ -680,8 +736,14 @@ Joining, Preloading and eager loading of these associations is deprecated and wi
end
end
+ def join_keys(assoc_klass)
+ source_reflection.join_keys(assoc_klass)
+ end
+
# The macro used by the source association
def source_macro
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.source_macro is deprecated and " \
+ "will be removed without replacement.")
source_reflection.source_macro
end
@@ -747,6 +809,10 @@ directive on your declaration like:
through_reflection.options
end
+ def join_id_for(owner) # :nodoc:
+ source_reflection.join_id_for(owner)
+ end
+
def check_validity!
if through_reflection.nil?
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
@@ -777,15 +843,25 @@ directive on your declaration like:
protected
- def actual_source_reflection # FIXME: this is a horrible name
- source_reflection.actual_source_reflection
- end
+ def actual_source_reflection # FIXME: this is a horrible name
+ source_reflection.send(:actual_source_reflection)
+ end
+
+ def primary_key(klass)
+ klass.primary_key || raise(UnknownPrimaryKey.new(klass))
+ end
private
def derive_class_name
# get the class_name of the belongs_to association of the through reflection
options[:source_type] || source_reflection.class_name
end
+
+ delegate_methods = AssociationReflection.public_instance_methods -
+ public_instance_methods
+
+ delegate(*delegate_methods, to: :delegate_reflection)
+
end
end
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index cef40be7ce..ad54d84665 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -242,6 +242,11 @@ module ActiveRecord
@records
end
+ # Serializes the relation objects Array.
+ def encode_with(coder)
+ coder.represent_seq(nil, to_a)
+ end
+
def as_json(options = nil) #:nodoc:
to_a.as_json(options)
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 29fc150b3d..b069cdce7c 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
module Batches
# Looping through a collection of records from the database
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 4bfd0167a4..0a5546a760 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Schema
#
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 64bc68eefd..fae6427ea1 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -92,16 +92,9 @@ HEADER
def tables(stream)
sorted_tables = @connection.tables.sort
- sorted_tables.each do |tbl|
- next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
- case ignored
- when String; remove_prefix_and_suffix(tbl) == ignored
- when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
- else
- raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
- end
- end
- table(tbl, stream)
+
+ sorted_tables.each do |table_name|
+ table(table_name, stream) unless ignored?(table_name)
end
# dump foreign keys at the end to make sure all dependent tables exist.
@@ -120,7 +113,8 @@ HEADER
# first dump primary key column
if @connection.respond_to?(:pk_and_sequence_for)
pk, _ = @connection.pk_and_sequence_for(table)
- elsif @connection.respond_to?(:primary_key)
+ end
+ if !pk && @connection.respond_to?(:primary_key)
pk = @connection.primary_key(table)
end
@@ -253,5 +247,16 @@ HEADER
def remove_prefix_and_suffix(table)
table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
end
+
+ def ignored?(table_name)
+ ['schema_migrations', ignore_tables].flatten.any? do |ignored|
+ case ignored
+ when String; remove_prefix_and_suffix(table_name) == ignored
+ when Regexp; remove_prefix_and_suffix(table_name) =~ ignored
+ else
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 9bc23b5ec7..892c78e479 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -110,6 +110,8 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
+ rescue ActiveRecord::NoDatabaseError
+ $stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
$stderr.puts error, *(error.backtrace)
$stderr.puts "Couldn't drop #{configuration['database']}"
@@ -125,6 +127,16 @@ module ActiveRecord
}
end
+ def migrate
+ verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
+ version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
+ scope = ENV['SCOPE']
+ Migration.verbose = verbose
+ Migrator.migrate(Migrator.migrations_paths, version) do |migration|
+ scope.blank? || scope == migration.scope
+ end
+ end
+
def charset_current(environment = env)
charset ActiveRecord::Base.configurations[environment]
end
@@ -172,20 +184,39 @@ module ActiveRecord
end
def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ This method will act on a specific connection in the future.
+ To act on the current connection, use `load_schema_current` instead.
+ MESSAGE
+ load_schema_current(format, file)
+ end
+
+ # This method is the successor of +load_schema+. We should rename it
+ # after +load_schema+ went through a deprecation cycle. (Rails > 4.2)
+ def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
case format
when :ruby
file ||= File.join(db_dir, "schema.rb")
check_schema_file(file)
+ purge(configuration)
+ ActiveRecord::Base.establish_connection(configuration)
load(file)
when :sql
file ||= File.join(db_dir, "structure.sql")
check_schema_file(file)
- structure_load(current_config, file)
+ purge(configuration)
+ structure_load(configuration, file)
else
raise ArgumentError, "unknown format #{format.inspect}"
end
end
+ def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
+ each_current_configuration(environment) { |configuration|
+ load_schema_for configuration, format, file
+ }
+ end
+
def check_schema_file(filename)
unless File.exist?(filename)
message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 644c4852b9..d890196f47 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -42,7 +42,7 @@ module ActiveRecord
end
def purge
- establish_connection :test
+ establish_connection configuration
connection.recreate_database configuration['database'], creation_options
end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 3d02ee07d0..ce1de4b76e 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -54,7 +54,7 @@ module ActiveRecord
command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
raise 'Error dumping database' unless Kernel.system(command)
- File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
+ File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
end
def structure_load(filename)
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 e2e37e7c00..ddf3e1804c 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,4 +1,3 @@
-
module ActiveRecord
# = Active Record Timestamp
#
diff --git a/activerecord/lib/active_record/type/decimal.rb b/activerecord/lib/active_record/type/decimal.rb
index a9db51c6ba..d10778eeb6 100644
--- a/activerecord/lib/active_record/type/decimal.rb
+++ b/activerecord/lib/active_record/type/decimal.rb
@@ -14,10 +14,25 @@ module ActiveRecord
private
def cast_value(value)
- if value.respond_to?(:to_d)
- value.to_d
+ case value
+ when ::Float
+ BigDecimal(value, float_precision)
+ when ::Numeric, ::String
+ BigDecimal(value, precision.to_i)
else
- value.to_s.to_d
+ if value.respond_to?(:to_d)
+ value.to_d
+ else
+ cast_value(value.to_s)
+ end
+ end
+ end
+
+ def float_precision
+ if precision.to_i > ::Float::DIG + 1
+ ::Float::DIG + 1
+ else
+ precision.to_i
end
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 42bbed7103..abeea769c4 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -12,7 +12,7 @@ module ActiveRecord
end
def type_cast_from_database(value)
- if is_default_value?(value)
+ if default_value?(value)
value
else
coder.load(super)
@@ -21,7 +21,7 @@ module ActiveRecord
def type_cast_for_database(value)
return if value.nil?
- unless is_default_value?(value)
+ unless default_value?(value)
super coder.dump(value)
end
end
@@ -43,7 +43,7 @@ module ActiveRecord
private
- def is_default_value?(value)
+ def default_value?(value)
value == coder.load(nil)
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index b4b33804de..7f7d49cdb4 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -29,21 +29,6 @@ module ActiveRecord
extend ActiveSupport::Concern
include ActiveModel::Validations
- module ClassMethods
- # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
- # so an exception is raised if the record is invalid.
- def create!(attributes = nil, &block)
- if attributes.is_a?(Array)
- attributes.collect { |attr| create!(attr, &block) }
- else
- object = new(attributes)
- yield(object) if block_given?
- object.save!
- object
- end
- end
- end
-
# The validation process on save can be skipped by passing <tt>validate: false</tt>.
# The regular Base#save method is replaced with this when the validations
# module is mixed in, which it is by default.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2a34969a8c..2dba4c7b94 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -152,7 +152,7 @@ module ActiveRecord
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
- # determine if the validation should ot occur (e.g. <tt>unless: :skip_validation</tt>,
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a +true+ or +false+
# value.
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 778c4ed7e5..6f84bae432 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -192,7 +192,7 @@ module ActiveRecord
def test_select_methods_passing_a_association_relation
author = Author.create!(name: 'john')
Post.create!(author: author, title: 'foo', body: 'bar')
- query = author.posts.select(:title)
+ query = author.posts.where(title: 'foo').select(:title)
assert_equal({"title" => "foo"}, @connection.select_one(query.arel, nil, query.bind_values))
assert_equal({"title" => "foo"}, @connection.select_one(query))
assert @connection.select_all(query).is_a?(ActiveRecord::Result)
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 4c90d06732..b0759dffde 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -134,12 +134,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert_equal [["STRICT_ALL_TABLES"]], result.rows
end
- def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
+ def test_mysql_strict_mode_disabled
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false}))
- global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
- session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal global_sql_mode.rows, session_sql_mode.rows
+ result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 65f50e77bb..3b35e69e0d 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -60,12 +60,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert_equal [["STRICT_ALL_TABLES"]], result.rows
end
- def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
+ def test_mysql_strict_mode_disabled
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false}))
- global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
- session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal global_sql_mode.rows, session_sql_mode.rows
+ result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index ec73ec35aa..9c49599d34 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -6,12 +6,12 @@ module ActiveRecord
class SchemaMigrationsTest < ActiveRecord::TestCase
def test_renaming_index_on_foreign_key
connection.add_index "engines", "car_id"
- connection.execute "ALTER TABLE engines ADD CONSTRAINT fk_engines_cars FOREIGN KEY (car_id) REFERENCES cars(id)"
+ connection.add_foreign_key :engines, :cars, name: "fk_engines_cars"
connection.rename_index("engines", "index_engines_on_car_id", "idx_renamed")
assert_equal ["idx_renamed"], connection.indexes("engines").map(&:name)
ensure
- connection.execute "ALTER TABLE engines DROP FOREIGN KEY fk_engines_cars"
+ connection.remove_foreign_key :engines, name: "fk_engines_cars"
end
def test_initializes_schema_migrations_for_encoding_utf8mb4
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index cb3c02fa3a..86ba849445 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -4,7 +4,7 @@ require "cases/helper"
require 'active_record/base'
require 'active_record/connection_adapters/postgresql_adapter'
-class PostgresqlJSONTest < ActiveRecord::TestCase
+module PostgresqlJSONSharedTestCases
class JsonDataType < ActiveRecord::Base
self.table_name = 'json_data_type'
@@ -16,8 +16,8 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
begin
@connection.transaction do
@connection.create_table('json_data_type') do |t|
- t.json 'payload', :default => {}
- t.json 'settings'
+ t.public_send column_type, 'payload', default: {} # t.json 'payload', default: {}
+ t.public_send column_type, 'settings' # t.json 'settings'
end
end
rescue ActiveRecord::StatementInvalid
@@ -26,21 +26,21 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
@column = JsonDataType.columns_hash['payload']
end
- teardown do
+ def teardown
@connection.execute 'drop table if exists json_data_type'
end
def test_column
column = JsonDataType.columns_hash["payload"]
- assert_equal :json, column.type
- assert_equal "json", column.sql_type
+ assert_equal column_type, column.type
+ assert_equal column_type.to_s, column.sql_type
assert_not column.number?
assert_not column.binary?
assert_not column.array
end
def test_default
- @connection.add_column 'json_data_type', 'permissions', :json, default: '{"users": "read", "posts": ["read", "write"]}'
+ @connection.add_column 'json_data_type', 'permissions', column_type, default: '{"users": "read", "posts": ["read", "write"]}'
JsonDataType.reset_column_information
assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults['permissions'])
@@ -52,11 +52,11 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
def test_change_table_supports_json
@connection.transaction do
@connection.change_table('json_data_type') do |t|
- t.json 'users', default: '{}'
+ t.public_send column_type, 'users', default: '{}' # t.json 'users', default: '{}'
end
JsonDataType.reset_column_information
column = JsonDataType.columns_hash['users']
- assert_equal :json, column.type
+ assert_equal column_type, column.type
raise ActiveRecord::Rollback # reset the schema change
end
@@ -175,3 +175,19 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
assert_not json.changed?
end
end
+
+class PostgresqlJSONTest < ActiveRecord::TestCase
+ include PostgresqlJSONSharedTestCases
+
+ def column_type
+ :json
+ end
+end
+
+class PostgresqlJSONBTest < ActiveRecord::TestCase
+ include PostgresqlJSONSharedTestCases
+
+ def column_type
+ :jsonb
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index f5b199b46a..66006d718f 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -60,6 +60,43 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
assert_equal(nil, UUIDType.last.guid)
end
+ def test_treat_invalid_uuid_as_nil
+ uuid = UUIDType.create! guid: 'foobar'
+ assert_equal(nil, uuid.guid)
+ end
+
+ def test_invalid_uuid_dont_modify_before_type_cast
+ uuid = UUIDType.new guid: 'foobar'
+ assert_equal 'foobar', uuid.guid_before_type_cast
+ end
+
+ def test_rfc_4122_regex
+ # Valid uuids
+ ['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11',
+ '{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}',
+ 'a0eebc999c0b4ef8bb6d6bb9bd380a11',
+ 'a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11',
+ '{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}'].each do |valid_uuid|
+ uuid = UUIDType.new guid: valid_uuid
+ assert_not_nil uuid.guid
+ end
+
+ # Invalid uuids
+ [['A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'],
+ Hash.new,
+ 0,
+ 0.0,
+ true,
+ 'Z0000C99-9C0B-4EF8-BB6D-6BB9BD380A11',
+ '{a0eebc99-9c0b-4ef8-fb6d-6bb9bd380a11}',
+ 'a0eebc999r0b4ef8ab6d6bb9bd380a11',
+ 'a0ee-bc99------4ef8-bb6d-6bb9-bd38-0a11',
+ '{a0eebc99-bb6d6bb9-bd380a11}'].each do |invalid_uuid|
+ uuid = UUIDType.new guid: invalid_uuid
+ assert_nil uuid.guid
+ end
+ end
+
def test_uuid_formats
["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
"{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
@@ -87,10 +124,26 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
t.string 'name'
t.uuid 'other_uuid', default: 'uuid_generate_v4()'
end
+
+ # Create custom PostgreSQL function to generate UUIDs
+ # to test dumping tables which columns have defaults with custom functions
+ connection.execute <<-SQL
+ CREATE OR REPLACE FUNCTION my_uuid_generator() RETURNS uuid
+ AS $$ SELECT * FROM uuid_generate_v4() $$
+ LANGUAGE SQL VOLATILE;
+ SQL
+
+ # Create such a table with custom function as default value generator
+ connection.create_table('pg_uuids_2', id: :uuid, default: 'my_uuid_generator()') do |t|
+ t.string 'name'
+ t.uuid 'other_uuid_2', default: 'my_uuid_generator()'
+ end
end
teardown do
drop_table "pg_uuids"
+ drop_table 'pg_uuids_2'
+ connection.execute 'DROP FUNCTION IF EXISTS my_uuid_generator();'
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -122,6 +175,13 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string)
assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string)
end
+
+ def test_schema_dumper_for_uuid_primary_key_with_custom_default
+ schema = StringIO.new
+ ActiveRecord::SchemaDumper.dump(connection, schema)
+ assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema.string)
+ assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema.string)
+ end
end
end
@@ -173,7 +233,7 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
t.string 'title'
end
connection.create_table('pg_uuid_comments', id: :uuid) do |t|
- t.uuid :uuid_post_id, default: 'uuid_generate_v4()'
+ t.references :uuid_post, type: :uuid
t.string 'content'
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 3bd53aa278..ac8332e2fa 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -103,6 +103,13 @@ module ActiveRecord
}.new
assert_raise(TypeError) { @conn.type_cast(quoted_id_obj, nil) }
end
+
+ def test_quoting_binary_strings
+ value = "hello".encode('ascii-8bit')
+ column = Column.new(nil, 1, SQLite3String.new)
+
+ assert_equal "'hello'", @conn.quote(value, column)
+ end
end
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 3e5b7e655b..fe961e871c 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -837,6 +837,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_calling_empty_with_counter_cache
+ post = posts(:welcome)
+ assert_queries(0) do
+ assert_not post.comments.empty?
+ end
+ end
+
def test_custom_named_counter_cache
topic = topics(:first)
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 3720d6d251..31b68c940e 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -130,7 +130,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_through_has_one_through_with_has_one_source_reflection_preload
members = assert_queries(4) { Member.includes(:nested_sponsors).to_a }
mustache = sponsors(:moustache_club_sponsor_for_groucho)
- assert_no_queries do
+ assert_no_queries(ignore_none: false) do
assert_equal [mustache], members.first.nested_sponsors
end
end
diff --git a/activerecord/test/cases/attribute_decorators_test.rb b/activerecord/test/cases/attribute_decorators_test.rb
index b352d1a6c2..cbc2c4e5d7 100644
--- a/activerecord/test/cases/attribute_decorators_test.rb
+++ b/activerecord/test/cases/attribute_decorators_test.rb
@@ -45,7 +45,6 @@ module ActiveRecord
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
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 2048e0d5ad..ab67cf4085 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -143,7 +143,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# Syck calls respond_to? before actually calling initialize
def test_respond_to_with_allocated_object
- topic = Topic.allocate
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'topics'
+ end
+
+ topic = klass.allocate
assert !topic.respond_to?("nothingness")
assert !topic.respond_to?(:nothingness)
assert_respond_to topic, "title"
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
index cdbb11fa32..dc20c3c676 100644
--- a/activerecord/test/cases/attribute_set_test.rb
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -88,15 +88,15 @@ module ActiveRecord
assert_equal [:foo], attributes.keys
end
- test "uninitialized attributes return false for include?" do
+ test "uninitialized attributes return false for key?" do
attributes = attributes_with_uninitialized_key
- assert attributes.include?(:foo)
- assert_not attributes.include?(:bar)
+ assert attributes.key?(:foo)
+ assert_not attributes.key?(:bar)
end
- test "unknown attributes return false for include?" do
+ test "unknown attributes return false for key?" do
attributes = attributes_with_uninitialized_key
- assert_not attributes.include?(:wibble)
+ assert_not attributes.key?(:wibble)
end
test "fetch_value returns the value for the given initialized attribute" do
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index 24452fdec2..91f6aee931 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -4,7 +4,7 @@ require 'minitest/mock'
module ActiveRecord
class AttributeTest < ActiveRecord::TestCase
setup do
- @type = MiniTest::Mock.new
+ @type = Minitest::Mock.new
end
teardown do
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 8f83cf7cb4..4c0b0c868a 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1347,14 +1347,32 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_compute_type_no_method_error
- ActiveSupport::Dependencies.stubs(:constantize).raises(NoMethodError)
+ ActiveSupport::Dependencies.stubs(:safe_constantize).raises(NoMethodError)
assert_raises NoMethodError do
ActiveRecord::Base.send :compute_type, 'InvalidModel'
end
end
+ def test_compute_type_on_undefined_method
+ error = nil
+ begin
+ Class.new(Author) do
+ alias_method :foo, :bar
+ end
+ rescue => e
+ error = e
+ end
+
+ ActiveSupport::Dependencies.stubs(:safe_constantize).raises(e)
+
+ exception = assert_raises NameError do
+ ActiveRecord::Base.send :compute_type, 'InvalidModel'
+ end
+ assert_equal error.message, exception.message
+ end
+
def test_compute_type_argument_error
- ActiveSupport::Dependencies.stubs(:constantize).raises(ArgumentError)
+ ActiveSupport::Dependencies.stubs(:safe_constantize).raises(ArgumentError)
assert_raises ArgumentError do
ActiveRecord::Base.send :compute_type, 'InvalidModel'
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index ea73c561e9..69a7f25213 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -169,7 +169,19 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.create!(:catchphrase => 'Yar!')
pirate.catchphrase = 'Ahoy!'
- pirate.reset_catchphrase!
+ assert_deprecated do
+ pirate.reset_catchphrase!
+ end
+ assert_equal "Yar!", pirate.catchphrase
+ assert_equal Hash.new, pirate.changes
+ assert !pirate.catchphrase_changed?
+ end
+
+ def test_restore_attribute!
+ pirate = Pirate.create!(:catchphrase => 'Yar!')
+ pirate.catchphrase = 'Ahoy!'
+
+ pirate.restore_catchphrase!
assert_equal "Yar!", pirate.catchphrase
assert_equal Hash.new, pirate.changes
assert !pirate.catchphrase_changed?
@@ -398,7 +410,7 @@ class DirtyTest < ActiveRecord::TestCase
def test_dup_objects_should_not_copy_dirty_flag_from_creator
pirate = Pirate.create!(:catchphrase => "shiver me timbers")
pirate_dup = pirate.dup
- pirate_dup.reset_catchphrase!
+ pirate_dup.restore_catchphrase!
pirate.catchphrase = "I love Rum"
assert pirate.catchphrase_changed?
assert !pirate_dup.catchphrase_changed?
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 937646b09a..a35ddf5632 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -199,3 +199,10 @@ module InTimeZone
ActiveRecord::Base.time_zone_aware_attributes = old_tz
end
end
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index a6d506b04a..3e9d957ed3 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -72,6 +72,20 @@ module ActiveRecord
end
end
+ def test_references_column_type_with_polymorphic_and_type
+ with_change_table do |t|
+ @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true, type: :string]
+ t.references :taggable, polymorphic: true, type: :string
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_and_type
+ with_change_table do |t|
+ @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true, type: :string]
+ t.remove_references :taggable, polymorphic: true, type: :string
+ end
+ end
+
def test_timestamps_creates_updated_at_and_created_at
with_change_table do |t|
@connection.expect :add_timestamps, nil, [:delete_me]
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 93adbdd05b..763aa88f72 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -51,46 +51,46 @@ module ActiveRecord
end
end
- # We specifically do a manual INSERT here, and then test only the SELECT
- # functionality. This allows us to more easily catch INSERT being broken,
- # but SELECT actually working fine.
- def test_native_decimal_insert_manual_vs_automatic
- correct_value = '0012345678901234567890.0123456789'.to_d
-
- connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
-
- # 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?(: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)"
- else
- connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)"
- end
+ unless current_adapter?(:SQLite3Adapter)
+ # We specifically do a manual INSERT here, and then test only the SELECT
+ # functionality. This allows us to more easily catch INSERT being broken,
+ # but SELECT actually working fine.
+ def test_native_decimal_insert_manual_vs_automatic
+ correct_value = '0012345678901234567890.0123456789'.to_d
+
+ connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
+
+ # 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?(: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)"
+ else
+ connection.execute "insert into test_models (wealth) values (12345678901234567890.0123456789)"
+ end
- # SELECT
- row = TestModel.first
- assert_kind_of BigDecimal, row.wealth
+ # SELECT
+ row = TestModel.first
+ assert_kind_of BigDecimal, row.wealth
- # If this assert fails, that means the SELECT is broken!
- unless current_adapter?(:SQLite3Adapter)
- assert_equal correct_value, row.wealth
- end
+ # If this assert fails, that means the SELECT is broken!
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal correct_value, row.wealth
+ end
- # Reset to old state
- TestModel.delete_all
+ # Reset to old state
+ TestModel.delete_all
- # Now use the Rails insertion
- TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789")
+ # Now use the Rails insertion
+ TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789")
- # SELECT
- row = TestModel.first
- assert_kind_of BigDecimal, row.wealth
+ # SELECT
+ row = TestModel.first
+ assert_kind_of BigDecimal, row.wealth
- # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
- unless current_adapter?(:SQLite3Adapter)
+ # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
assert_equal correct_value, row.wealth
end
end
@@ -121,54 +121,54 @@ module ActiveRecord
end
end
- def test_native_types
- add_column "test_models", "first_name", :string
- add_column "test_models", "last_name", :string
- add_column "test_models", "bio", :text
- add_column "test_models", "age", :integer
- add_column "test_models", "height", :float
- add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
- add_column "test_models", "birthday", :datetime
- add_column "test_models", "favorite_day", :date
- add_column "test_models", "moment_of_truth", :datetime
- add_column "test_models", "male", :boolean
-
- TestModel.create :first_name => 'bob', :last_name => 'bobsen',
- :bio => "I was born ....", :age => 18, :height => 1.78,
- :wealth => BigDecimal.new("12345678901234567890.0123456789"),
- :birthday => 18.years.ago, :favorite_day => 10.days.ago,
- :moment_of_truth => "1782-10-10 21:40:18", :male => true
-
- bob = TestModel.first
- assert_equal 'bob', bob.first_name
- assert_equal 'bobsen', bob.last_name
- assert_equal "I was born ....", bob.bio
- assert_equal 18, bob.age
-
- # Test for 30 significant digits (beyond the 16 of float), 10 of them
- # after the decimal place.
-
- unless current_adapter?(:SQLite3Adapter)
+ unless current_adapter?(:SQLite3Adapter)
+ def test_native_types
+ add_column "test_models", "first_name", :string
+ add_column "test_models", "last_name", :string
+ add_column "test_models", "bio", :text
+ add_column "test_models", "age", :integer
+ add_column "test_models", "height", :float
+ add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
+ add_column "test_models", "birthday", :datetime
+ add_column "test_models", "favorite_day", :date
+ add_column "test_models", "moment_of_truth", :datetime
+ add_column "test_models", "male", :boolean
+
+ TestModel.create :first_name => 'bob', :last_name => 'bobsen',
+ :bio => "I was born ....", :age => 18, :height => 1.78,
+ :wealth => BigDecimal.new("12345678901234567890.0123456789"),
+ :birthday => 18.years.ago, :favorite_day => 10.days.ago,
+ :moment_of_truth => "1782-10-10 21:40:18", :male => true
+
+ bob = TestModel.first
+ assert_equal 'bob', bob.first_name
+ assert_equal 'bobsen', bob.last_name
+ assert_equal "I was born ....", bob.bio
+ assert_equal 18, bob.age
+
+ # Test for 30 significant digits (beyond the 16 of float), 10 of them
+ # after the decimal place.
+
assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
- end
- assert_equal true, bob.male?
+ assert_equal true, bob.male?
- assert_equal String, bob.first_name.class
- assert_equal String, bob.last_name.class
- assert_equal String, bob.bio.class
- assert_equal Fixnum, bob.age.class
- assert_equal Time, bob.birthday.class
+ assert_equal String, bob.first_name.class
+ assert_equal String, bob.last_name.class
+ assert_equal String, bob.bio.class
+ assert_equal Fixnum, bob.age.class
+ assert_equal Time, bob.birthday.class
- 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
- end
+ 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
+ end
- assert_instance_of TrueClass, bob.male?
- assert_kind_of BigDecimal, bob.wealth
+ assert_instance_of TrueClass, bob.male?
+ assert_kind_of BigDecimal, bob.wealth
+ end
end
if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 62b60f7f7b..bea9d6b2c9 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -119,6 +119,30 @@ module ActiveRecord
assert !connection.tables.include?('artists_musics')
end
+
+ def test_create_and_drop_join_table_with_common_prefix
+ with_table_cleanup do
+ connection.create_join_table 'audio_artists', 'audio_musics'
+ assert_includes connection.tables, 'audio_artists_musics'
+
+ connection.drop_join_table 'audio_artists', 'audio_musics'
+ assert !connection.tables.include?('audio_artists_musics'), "Should have dropped join table, but didn't"
+ end
+ end
+
+ private
+
+ def with_table_cleanup
+ tables_before = connection.tables
+
+ yield
+ ensure
+ tables_after = connection.tables - tables_before
+
+ tables_after.each do |table|
+ connection.execute "DROP TABLE #{table}"
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb
index 517ee695ce..7afac83bd2 100644
--- a/activerecord/test/cases/migration/pending_migrations_test.rb
+++ b/activerecord/test/cases/migration/pending_migrations_test.rb
@@ -6,8 +6,8 @@ module ActiveRecord
class PendingMigrationsTest < ActiveRecord::TestCase
def setup
super
- @connection = MiniTest::Mock.new
- @app = MiniTest::Mock.new
+ @connection = Minitest::Mock.new
+ @app = Minitest::Mock.new
conn = @connection
@pending = Class.new(CheckPending) {
define_method(:connection) { conn }
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index e9545f2cce..b8b4fa1135 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -55,6 +55,11 @@ module ActiveRecord
assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id')
end
+ def test_creates_reference_id_with_specified_type
+ add_reference table_name, :user, type: :string
+ assert column_exists?(table_name, :user_id, :string)
+ end
+
def test_deletes_reference_id_column
remove_reference table_name, :supplier
assert_not column_exists?(table_name, :supplier_id, :integer)
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 6b840e16bb..ef3f073472 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -63,7 +63,7 @@ class MigrationTest < ActiveRecord::TestCase
end
Person.connection.remove_column("people", "first_name") rescue nil
Person.connection.remove_column("people", "middle_name") rescue nil
- Person.connection.add_column("people", "first_name", :string, :limit => 40)
+ Person.connection.add_column("people", "first_name", :string)
Person.reset_column_information
end
@@ -895,4 +895,14 @@ class CopyMigrationsTest < ActiveRecord::TestCase
ensure
ActiveRecord::Base.logger = old
end
+
+ private
+
+ def quietly
+ silence_stream(STDOUT) do
+ silence_stream(STDERR) do
+ yield
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index b04df7ce43..f19a6ea5e3 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -5,6 +5,7 @@ require 'models/subscriber'
require 'models/movie'
require 'models/keyboard'
require 'models/mixed_case_monkey'
+require 'models/dashboard'
class PrimaryKeysTest < ActiveRecord::TestCase
fixtures :topics, :subscribers, :movies, :mixed_case_monkeys
@@ -164,6 +165,15 @@ class PrimaryKeysTest < ActiveRecord::TestCase
MixedCaseMonkey.reset_primary_key
assert_equal "monkeyID", MixedCaseMonkey.primary_key
end
+
+ def test_primary_key_update_with_custom_key_name
+ dashboard = Dashboard.create!(dashboard_id: '1')
+ dashboard.id = '2'
+ dashboard.save!
+
+ dashboard = Dashboard.first
+ assert_equal '2', dashboard.id
+ end
end
class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index b41a7ee787..84abaf0291 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -87,7 +87,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_reflection_klass_for_nested_class_name
- reflection = MacroReflection.new(:company, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base)
+ reflection = ActiveRecord::Reflection.create(:has_many, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base)
assert_nothing_raised do
assert_equal MyApplication::Business::Company, reflection.klass
end
@@ -97,21 +97,21 @@ class ReflectionTest < ActiveRecord::TestCase
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'plural_irregular', 'plurales_irregulares'
end
- reflection = AssociationReflection.new(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base)
+ reflection = ActiveRecord::Reflection.create(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base)
assert_equal 'PluralIrregular', reflection.class_name
end
def test_aggregation_reflection
reflection_for_address = AggregateReflection.new(
- :composed_of, :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
+ :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
)
reflection_for_balance = AggregateReflection.new(
- :composed_of, :balance, nil, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
+ :balance, nil, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
)
reflection_for_gps_location = AggregateReflection.new(
- :composed_of, :gps_location, nil, { }, Customer
+ :gps_location, nil, { }, Customer
)
assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
@@ -135,7 +135,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_many_reflection
- reflection_for_clients = AssociationReflection.new(:has_many, :clients, nil, { :order => "id", :dependent => :destroy }, Firm)
+ reflection_for_clients = ActiveRecord::Reflection.create(:has_many, :clients, nil, { :order => "id", :dependent => :destroy }, Firm)
assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)
@@ -147,7 +147,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_has_one_reflection
- reflection_for_account = AssociationReflection.new(:has_one, :account, nil, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
+ reflection_for_account = ActiveRecord::Reflection.create(:has_one, :account, nil, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
assert_equal reflection_for_account, Firm.reflect_on_association(:account)
assert_equal Account, Firm.reflect_on_association(:account).klass
@@ -284,12 +284,12 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_association_primary_key_raises_when_missing_primary_key
- reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :edge, nil, {}, Author)
+ reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author)
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key }
through = Class.new(ActiveRecord::Reflection::ThroughReflection) {
define_method(:source_reflection) { reflection }
- }.new(:fuu, :edge, nil, {}, Author)
+ }.new(reflection)
assert_raises(ActiveRecord::UnknownPrimaryKey) { through.association_primary_key }
end
@@ -299,7 +299,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_active_record_primary_key_raises_when_missing_primary_key
- reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :author, nil, {}, Edge)
+ reflection = ActiveRecord::Reflection.create(:has_many, :author, nil, {}, Edge)
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key }
end
@@ -317,32 +317,28 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_default_association_validation
- assert AssociationReflection.new(:has_many, :clients, nil, {}, Firm).validate?
+ assert ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm).validate?
- assert !AssociationReflection.new(:has_one, :client, nil, {}, Firm).validate?
- assert !AssociationReflection.new(:belongs_to, :client, nil, {}, Firm).validate?
- assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, {}, Firm).validate?
+ assert !ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm).validate?
+ assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm).validate?
end
def test_always_validate_association_if_explicit
- assert AssociationReflection.new(:has_one, :client, nil, { :validate => true }, Firm).validate?
- assert AssociationReflection.new(:belongs_to, :client, nil, { :validate => true }, Firm).validate?
- assert AssociationReflection.new(:has_many, :clients, nil, { :validate => true }, Firm).validate?
- assert AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, { :validate => true }, Firm).validate?
+ assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :validate => true }, Firm).validate?
+ assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :validate => true }, Firm).validate?
+ assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :validate => true }, Firm).validate?
end
def test_validate_association_if_autosave
- assert AssociationReflection.new(:has_one, :client, nil, { :autosave => true }, Firm).validate?
- assert AssociationReflection.new(:belongs_to, :client, nil, { :autosave => true }, Firm).validate?
- assert AssociationReflection.new(:has_many, :clients, nil, { :autosave => true }, Firm).validate?
- assert AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, { :autosave => true }, Firm).validate?
+ assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true }, Firm).validate?
+ assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true }, Firm).validate?
+ assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true }, Firm).validate?
end
def test_never_validate_association_if_explicit
- assert !AssociationReflection.new(:has_one, :client, nil, { :autosave => true, :validate => false }, Firm).validate?
- assert !AssociationReflection.new(:belongs_to, :client, nil, { :autosave => true, :validate => false }, Firm).validate?
- assert !AssociationReflection.new(:has_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate?
- assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate?
+ assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true, :validate => false }, Firm).validate?
+ assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true, :validate => false }, Firm).validate?
+ assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate?
end
def test_foreign_key
@@ -364,11 +360,11 @@ class ReflectionTest < ActiveRecord::TestCase
category = Struct.new(:table_name, :pluralize_table_names).new('categories', true)
product = Struct.new(:table_name, :pluralize_table_names).new('products', true)
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, nil, {}, product)
+ reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product)
reflection.stubs(:klass).returns(category)
assert_equal 'categories_products', reflection.join_table
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :products, nil, {}, category)
+ reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category)
reflection.stubs(:klass).returns(product)
assert_equal 'categories_products', reflection.join_table
end
@@ -377,11 +373,11 @@ class ReflectionTest < ActiveRecord::TestCase
category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true)
product = Struct.new(:table_name, :pluralize_table_names).new('catalog_products', true)
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, nil, {}, product)
+ reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product)
reflection.stubs(:klass).returns(category)
assert_equal 'catalog_categories_products', reflection.join_table
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :products, nil, {}, category)
+ reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category)
reflection.stubs(:klass).returns(product)
assert_equal 'catalog_categories_products', reflection.join_table
end
@@ -390,11 +386,11 @@ class ReflectionTest < ActiveRecord::TestCase
category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true)
page = Struct.new(:table_name, :pluralize_table_names).new('content_pages', true)
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, nil, {}, page)
+ reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, page)
reflection.stubs(:klass).returns(category)
assert_equal 'catalog_categories_content_pages', reflection.join_table
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :pages, nil, {}, category)
+ reflection = ActiveRecord::Reflection.create(:has_many, :pages, nil, {}, category)
reflection.stubs(:klass).returns(page)
assert_equal 'catalog_categories_content_pages', reflection.join_table
end
@@ -403,11 +399,11 @@ class ReflectionTest < ActiveRecord::TestCase
category = Struct.new(:table_name, :pluralize_table_names).new('categories', true)
product = Struct.new(:table_name, :pluralize_table_names).new('products', true)
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, nil, { :join_table => 'product_categories' }, product)
+ reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, { :join_table => 'product_categories' }, product)
reflection.stubs(:klass).returns(category)
assert_equal 'product_categories', reflection.join_table
- reflection = AssociationReflection.new(:has_and_belongs_to_many, :products, nil, { :join_table => 'product_categories' }, category)
+ reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { :join_table => 'product_categories' }, category)
reflection.stubs(:klass).returns(product)
assert_equal 'product_categories', reflection.join_table
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 186a1a2ade..f8d87a3661 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -3,10 +3,11 @@ require 'models/topic'
require 'models/reply'
require 'models/person'
require 'models/traffic_light'
+require 'models/post'
require 'bcrypt'
class SerializedAttributeTest < ActiveRecord::TestCase
- fixtures :topics
+ fixtures :topics, :posts
MyObject = Struct.new :attribute1, :attribute2
@@ -67,6 +68,40 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal(orig.content, clone.content)
end
+ def test_serialized_json_attribute_returns_unserialized_value
+ Topic.serialize :content, JSON
+ my_post = posts(:welcome)
+
+ t = Topic.new(content: my_post)
+ t.save!
+ t.reload
+
+ assert_instance_of(Hash, t.content)
+ assert_equal(my_post.id, t.content["id"])
+ assert_equal(my_post.title, t.content["title"])
+ end
+
+ def test_json_read_legacy_null
+ Topic.serialize :content, JSON
+
+ # Force a row to have a JSON "null" instead of a database NULL (this is how
+ # null values are saved on 4.1 and before)
+ id = Topic.connection.insert "INSERT INTO topics (content) VALUES('null')"
+ t = Topic.find(id)
+
+ assert_nil t.content
+ end
+
+ def test_json_read_db_null
+ Topic.serialize :content, JSON
+
+ # Force a row to have a database NULL instead of a JSON "null"
+ id = Topic.connection.insert "INSERT INTO topics (content) VALUES(NULL)"
+ t = Topic.find(id)
+
+ assert_nil t.content
+ end
+
def test_serialized_attribute_declared_in_subclass
hash = { 'important1' => 'value1', 'important2' => 'value2' }
important_topic = ImportantTopic.create("important" => hash)
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 0f48c8d5fc..01d373b691 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -273,6 +273,19 @@ module ActiveRecord
end
end
+ class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ def test_migrate_receives_correct_env_vars
+ verbose, version = ENV['VERBOSE'], ENV['VERSION']
+
+ ENV['VERBOSE'] = 'false'
+ ENV['VERSION'] = '4'
+
+ ActiveRecord::Migrator.expects(:migrate).with(ActiveRecord::Migrator.migrations_paths, 4)
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ ensure
+ ENV['VERBOSE'], ENV['VERSION'] = verbose, version
+ end
+ end
class DatabaseTasksPurgeTest < ActiveRecord::TestCase
include DatabaseTasksSetupper
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 3e3a2828f3..f58535f044 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
+if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
module ActiveRecord
class MysqlDBCreateTest < ActiveRecord::TestCase
def setup
@@ -196,8 +197,8 @@ module ActiveRecord
ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
- def test_establishes_connection_to_test_database
- ActiveRecord::Base.expects(:establish_connection).with(:test)
+ def test_establishes_connection_to_the_appropriate_database
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
ActiveRecord::Tasks::DatabaseTasks.purge @configuration
end
@@ -307,3 +308,4 @@ module ActiveRecord
end
end
+end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 6ea225178f..0d574d071c 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
+if current_adapter?(:PostgreSQLAdapter)
module ActiveRecord
class PostgreSQLDBCreateTest < ActiveRecord::TestCase
def setup
@@ -241,3 +242,4 @@ module ActiveRecord
end
end
+end
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index da3471adf9..750d5e42dc 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'pathname'
+if current_adapter?(:SQLite3Adapter)
module ActiveRecord
class SqliteDBCreateTest < ActiveRecord::TestCase
def setup
@@ -189,3 +190,4 @@ module ActiveRecord
end
end
end
+end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index b6c5511849..23a170388e 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -13,6 +13,23 @@ module ActiveRecord
assert_equal expected.to_s, actual.to_s, message
end
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ end
+
def capture_sql
SQLCounter.clear_log
yield
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index de1f624191..b4849222b8 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -424,6 +424,26 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_savepoints_name
+ Topic.transaction do
+ assert_nil Topic.connection.current_savepoint_name
+ assert_nil Topic.connection.current_transaction.savepoint_name
+
+ Topic.transaction(requires_new: true) do
+ assert_equal "active_record_1", Topic.connection.current_savepoint_name
+ assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name
+
+ Topic.transaction(requires_new: true) do
+ assert_equal "active_record_2", Topic.connection.current_savepoint_name
+ assert_equal "active_record_2", Topic.connection.current_transaction.savepoint_name
+ end
+
+ assert_equal "active_record_1", Topic.connection.current_savepoint_name
+ assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name
+ end
+ end
+ end
+
def test_rollback_when_commit_raises
Topic.connection.expects(:begin_db_transaction)
Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
@@ -526,13 +546,13 @@ class TransactionTest < ActiveRecord::TestCase
def test_transactions_state_from_rollback
connection = Topic.connection
- transaction = ActiveRecord::ConnectionAdapters::ClosedTransaction.new(connection).begin
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
assert transaction.open?
assert !transaction.state.rolledback?
assert !transaction.state.committed?
- transaction.perform_rollback
+ transaction.rollback
assert transaction.state.rolledback?
assert !transaction.state.committed?
@@ -540,13 +560,13 @@ class TransactionTest < ActiveRecord::TestCase
def test_transactions_state_from_commit
connection = Topic.connection
- transaction = ActiveRecord::ConnectionAdapters::ClosedTransaction.new(connection).begin
+ transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
assert transaction.open?
assert !transaction.state.rolledback?
assert !transaction.state.committed?
- transaction.perform_commit
+ transaction.commit
assert !transaction.state.rolledback?
assert transaction.state.committed?
diff --git a/activerecord/test/cases/type/decimal_test.rb b/activerecord/test/cases/type/decimal_test.rb
new file mode 100644
index 0000000000..da30de373e
--- /dev/null
+++ b/activerecord/test/cases/type/decimal_test.rb
@@ -0,0 +1,38 @@
+require "cases/helper"
+
+module ActiveRecord
+ module Type
+ class DecimalTest < ActiveRecord::TestCase
+ def test_type_cast_decimal
+ 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_decimal_from_float_with_large_precision
+ type = Decimal.new(precision: ::Float::DIG + 2)
+ assert_equal BigDecimal.new("123.0"), type.type_cast_from_user(123.0)
+ end
+
+ def test_type_cast_decimal_from_rational_with_precision
+ type = Decimal.new(precision: 2)
+ assert_equal BigDecimal("0.33"), type.type_cast_from_user(Rational(1, 3))
+ end
+
+ def test_type_cast_decimal_from_rational_without_precision_defaults_to_18_36
+ type = Decimal.new
+ assert_equal BigDecimal("0.333333333333333333E0"), type.type_cast_from_user(Rational(1, 3))
+ end
+
+ def test_type_cast_decimal_from_object_responding_to_d
+ value = Object.new
+ def value.to_d
+ BigDecimal.new("1")
+ end
+ type = Decimal.new
+ assert_equal BigDecimal("1"), type.type_cast_from_user(value)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb
index 47cf775cb6..5c54812f30 100644
--- a/activerecord/test/cases/types_test.rb
+++ b/activerecord/test/cases/types_test.rb
@@ -95,13 +95,6 @@ module ActiveRecord
assert_not type.changed?(nil, nil, nil)
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)
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 3790d3c8cf..4f38849131 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -52,14 +52,15 @@ class PresenceValidationTest < ActiveRecord::TestCase
end
def test_validates_presence_doesnt_convert_to_array
- Speedometer.validates_presence_of :dashboard
+ speedometer = Class.new(Speedometer)
+ speedometer.validates_presence_of :dashboard
dash = Dashboard.new
# dashboard has to_a method
def dash.to_a; ['(/)', '(\)']; end
- s = Speedometer.new
+ s = speedometer.new
s.dashboard = dash
assert_nothing_raised { s.valid? }
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 9f1d110ddb..bce59b4fcd 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
require 'models/topic'
+require 'models/reply'
require 'models/post'
require 'models/author'
diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb
index 3d7f0626e2..91e46f83e5 100644
--- a/activerecord/test/models/face.rb
+++ b/activerecord/test/models/face.rb
@@ -1,7 +1,8 @@
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
+ # Oracle identifier lengh is limited to 30 bytes or less, `polymorphic` renamed `poly`
+ belongs_to :poly_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 a26491ce61..4fbb6b226b 100644
--- a/activerecord/test/models/man.rb
+++ b/activerecord/test/models/man.rb
@@ -1,7 +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_one :polymorphic_face_without_inverse, :class_name => 'Face', :as => :poly_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/schema/schema.rb b/activerecord/test/schema/schema.rb
index 932c9ba5d9..a8b21904ac 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -781,8 +781,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 :poly_man_without_inverse_id
+ t.string :poly_man_without_inverse_type
t.integer :horrible_polymorphic_man_id
t.string :horrible_polymorphic_man_type
end
@@ -860,9 +860,8 @@ ActiveRecord::Schema.define do
create_table :fk_test_has_pk, force: true, primary_key: "pk_id" do |t|
end
- execute "ALTER TABLE fk_test_has_fk ADD CONSTRAINT fk_name FOREIGN KEY (#{quote_column_name 'fk_id'}) REFERENCES #{quote_table_name 'fk_test_has_pk'} (#{quote_column_name 'pk_id'})"
-
- 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'})"
+ add_foreign_key :fk_test_has_fk, :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
+ add_foreign_key :lessons_students, :students
end
create_table :overloaded_types, force: true do |t|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 005bcffa26..b961b373cb 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,46 @@
+* Fix ActiveSupport::TestCase not to order users' test cases by default.
+ If this change breaks your tests because your tests are order dependent, you need to explicitly call
+ ActiveSupport::TestCase.my_tests_are_order_dependent! at the top of your tests.
+
+ *Akira Matsuda*
+
+* Fix DateTime comparison with DateTime::Infinity object.
+
+ *Rafael Mendonça França*
+
+* Added Object#itself which returns the object itself. Useful when dealing with a chaining scenario, like Active Record scopes:
+
+ Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
+
+ *DHH*
+
+* `Object#with_options` executes block in merging option context when
+ explicit receiver in not passed.
+
+ *Pavel Pravosud*
+
+* Fixed a compatibility issue with the `Oj` gem when cherry-picking the file
+ `active_support/core_ext/object/json` without requiring `active_support/json`.
+
+ Fixes #16131.
+
+ *Godfrey Chan*
+
+* Make `Hash#with_indifferent_access` copy the default proc too.
+
+ *arthurnn*, *Xanders*
+
+* Add `String#truncate_words` to truncate a string by a number of words.
+
+ *Mohamed Osama*
+
+* Deprecate `capture` and `quietly`.
+
+ These methods are not thread safe and may cause issues when used in threaded environments.
+ To avoid problems we are deprecating them.
+
+ *Tom Meier*
+
* `DateTime#to_f` now preserves the fractional seconds instead of always
rounding to `.0`.
@@ -153,7 +196,7 @@
*Bogdan Gusiev*
-* Add `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5` to support stable
+* Add `Digest::UUID::uuid_v3` and `Digest::UUID::uuid_v5` to support stable
UUID fixtures on PostgreSQL.
*Roderick van Domburg*
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index f3625e8b79..c0b457c341 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
- s.add_dependency 'i18n', '~> 0.6', '>= 0.6.9'
+ s.add_dependency 'i18n', '>= 0.7.0.dev', '< 0.8'
s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index a3f672d4cc..ff67a6828c 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -237,7 +237,7 @@ module ActiveSupport
# seconds. Because of extended life of the previous cache, other processes
# will continue to use slightly stale data for a just a bit longer. In the
# meantime that first process will go ahead and will write into cache the
- # new value. After that all the processes will start getting new value.
+ # new value. After that all the processes will start getting the new value.
# The key is to keep <tt>:race_condition_ttl</tt> small.
#
# If the process regenerating the entry errors out, the entry will be
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 3529d57174..87ae052eb0 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -18,6 +18,11 @@ class Array
# ["3", "4"]
# ["5"]
def in_groups_of(number, fill_with = nil)
+ if number.to_i <= 0
+ raise ArgumentError,
+ "Group size must be a positive integer, was #{number.inspect}"
+ end
+
if fill_with == false
collection = self
else
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 289ca12b5e..dc4e767e9d 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -161,7 +161,9 @@ class DateTime
# Layers additional behavior on DateTime#<=> so that Time and
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- if other.respond_to? :to_datetime
+ if other.kind_of?(Infinity)
+ super
+ elsif other.respond_to? :to_datetime
super other.to_datetime
else
nil
diff --git a/activesupport/lib/active_support/core_ext/digest/uuid.rb b/activesupport/lib/active_support/core_ext/digest/uuid.rb
new file mode 100644
index 0000000000..593c51bba2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/digest/uuid.rb
@@ -0,0 +1,51 @@
+require 'securerandom'
+
+module Digest
+ module UUID
+ DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+ X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
+
+ # Generates a v5 non-random UUID (Universally Unique IDentifier).
+ #
+ # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
+ # uuid_from_hash always generates the same UUID for a given name and namespace combination.
+ #
+ # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt
+ def self.uuid_from_hash(hash_class, uuid_namespace, name)
+ if hash_class == Digest::MD5
+ version = 3
+ elsif hash_class == Digest::SHA1
+ version = 5
+ else
+ raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
+ end
+
+ hash = hash_class.new
+ hash.update(uuid_namespace)
+ hash.update(name)
+
+ ary = hash.digest.unpack('NnnnnN')
+ ary[2] = (ary[2] & 0x0FFF) | (version << 12)
+ ary[3] = (ary[3] & 0x3FFF) | 0x8000
+
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
+ end
+
+ # Convenience method for uuid_from_hash using Digest::MD5.
+ def self.uuid_v3(uuid_namespace, name)
+ self.uuid_from_hash(Digest::MD5, uuid_namespace, name)
+ end
+
+ # Convenience method for uuid_from_hash using Digest::SHA1.
+ def self.uuid_v5(uuid_namespace, name)
+ self.uuid_from_hash(Digest::SHA1, uuid_namespace, name)
+ end
+
+ # Convenience method for SecureRandom.uuid.
+ def self.uuid_v4
+ SecureRandom.uuid
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 8657f34be2..f4105f66b0 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -6,7 +6,8 @@ class Hash
# hash.transform_keys{ |key| key.to_s.upcase }
# # => {"NAME"=>"Rob", "AGE"=>"28"}
def transform_keys
- result = {}
+ return enum_for(:transform_keys) unless block_given?
+ result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
@@ -16,6 +17,7 @@ class Hash
# Destructively convert all keys using the block operations.
# Same as transform_keys but modifies +self+.
def transform_keys!
+ return enum_for(:transform_keys!) unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index 6ff7e91212..e9bcce761f 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -4,7 +4,8 @@ class Hash
#
# { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 }
# # => { a: 2, b: 4, c: 6 }
- def transform_values(&block)
+ def transform_values
+ return enum_for(:transform_values) unless block_given?
result = self.class.new
each do |key, value|
result[key] = yield(value)
@@ -14,6 +15,7 @@ class Hash
# Destructive +transform_values+
def transform_values!
+ return enum_for(:transform_values!) unless block_given?
each do |key, value|
self[key] = yield(value)
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index f3f8416905..80c531b694 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -31,9 +31,13 @@ module Kernel
# For compatibility
def silence_stderr #:nodoc:
+ ActiveSupport::Deprecation.warn(
+ "#silence_stderr is deprecated and will be removed in the next release"
+ ) #not thread-safe
silence_stream(STDERR) { yield }
end
+ # Deprecated : this method is not thread safe
# Silences any stream for the duration of the block.
#
# silence_stream(STDOUT) do
@@ -82,6 +86,9 @@ module Kernel
# stream = capture(:stderr) { system('echo error 1>&2') }
# stream # => "error\n"
def capture(stream)
+ ActiveSupport::Deprecation.warn(
+ "#capture(stream) is deprecated and will be removed in the next release"
+ ) #not thread-safe
stream = stream.to_s
captured_stream = Tempfile.new(stream)
stream_io = eval("$#{stream}")
@@ -105,6 +112,9 @@ module Kernel
#
# This method is not thread-safe.
def quietly
+ ActiveSupport::Deprecation.warn(
+ "#quietly is deprecated and will be removed in the next release"
+ ) #not thread-safe
silence_stream(STDOUT) do
silence_stream(STDERR) do
yield
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index f4f9152d6a..f1106cca9b 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/object/itself'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
diff --git a/activesupport/lib/active_support/core_ext/object/itself.rb b/activesupport/lib/active_support/core_ext/object/itself.rb
new file mode 100644
index 0000000000..adedc20169
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/itself.rb
@@ -0,0 +1,12 @@
+class Object
+ unless respond_to?(:itself) # TODO: Remove this file when we drop support for Ruby < 2.2
+ # Returns the object itself. Useful when dealing with a chaining scenario, like Active Record scopes:
+ #
+ # Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
+ #
+ # @return Object
+ def itself
+ self
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 5496692373..698b2d1920 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -162,7 +162,7 @@ end
class Time
def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
+ if ActiveSupport::JSON::Encoding.use_standard_json_time_format
xmlschema(ActiveSupport::JSON::Encoding.time_precision)
else
%(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
@@ -172,7 +172,7 @@ end
class Date
def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
+ if ActiveSupport::JSON::Encoding.use_standard_json_time_format
strftime("%Y-%m-%d")
else
strftime("%Y/%m/%d")
@@ -182,7 +182,7 @@ end
class DateTime
def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
+ if ActiveSupport::JSON::Encoding.use_standard_json_time_format
xmlschema(ActiveSupport::JSON::Encoding.time_precision)
else
strftime('%Y/%m/%d %H:%M:%S %z')
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 e65fc5bac1..684d4ef57e 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -1,60 +1 @@
-class Object
- # Alias of <tt>to_s</tt>.
- def to_param
- to_s
- end
-end
-
-class NilClass
- # Returns +self+.
- def to_param
- self
- end
-end
-
-class TrueClass
- # Returns +self+.
- def to_param
- self
- end
-end
-
-class FalseClass
- # Returns +self+.
- def to_param
- self
- end
-end
-
-class Array
- # Calls <tt>to_param</tt> on all its elements and joins the result with
- # slashes. This is used by <tt>url_for</tt> in Action Pack.
- def to_param
- collect { |e| e.to_param }.join '/'
- end
-end
-
-class Hash
- # Returns a string representation of the receiver suitable for use as a URL
- # query string:
- #
- # {name: 'David', nationality: 'Danish'}.to_param
- # # => "name=David&nationality=Danish"
- #
- # An optional namespace can be passed to enclose the param names:
- #
- # {name: 'David', nationality: 'Danish'}.to_param('user')
- # # => "user[name]=David&user[nationality]=Danish"
- #
- # The string pairs "key=value" that conform the query string
- # are sorted lexicographically in ascending order.
- #
- # This method is also aliased as +to_query+.
- def to_param(namespace = nil)
- collect do |key, value|
- unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
- value.to_query(namespace ? "#{namespace}[#{key}]" : key)
- end
- end.compact.sort! * '&'
- end
-end
+require 'active_support/core_ext/object/to_query'
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 172f06ed64..ccd568bbf5 100644
--- a/activesupport/lib/active_support/core_ext/object/to_query.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_query.rb
@@ -1,17 +1,46 @@
-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
- # param name.
- #
- # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work.
+ # Alias of <tt>to_s</tt>.
+ def to_param
+ to_s
+ end
+
+ # Converts an object into a string suitable for use as a URL query string,
+ # using the given <tt>key</tt> as the param name.
def to_query(key)
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
end
end
+class NilClass
+ # Returns +self+.
+ def to_param
+ self
+ end
+end
+
+class TrueClass
+ # Returns +self+.
+ def to_param
+ self
+ end
+end
+
+class FalseClass
+ # Returns +self+.
+ def to_param
+ self
+ end
+end
+
class Array
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
+ def to_param
+ collect { |e| e.to_param }.join '/'
+ end
+
# Converts an array into a string suitable for use as a URL query string,
# using the given +key+ as the param name.
#
@@ -28,5 +57,28 @@ class Array
end
class Hash
- alias_method :to_query, :to_param
+ # Returns a string representation of the receiver suitable for use as a URL
+ # query string:
+ #
+ # {name: 'David', nationality: 'Danish'}.to_query
+ # # => "name=David&nationality=Danish"
+ #
+ # An optional namespace can be passed to enclose key names:
+ #
+ # {name: 'David', nationality: 'Danish'}.to_query('user')
+ # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
+ #
+ # The string pairs "key=value" that conform the query string
+ # are sorted lexicographically in ascending order.
+ #
+ # This method is also aliased as +to_param+.
+ def to_query(namespace = nil)
+ collect do |key, value|
+ unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
+ end
+ end.compact.sort! * '&'
+ end
+
+ alias_method :to_param, :to_query
end
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index 42e388b065..42e87c4424 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -34,9 +34,22 @@ class Object
# body i18n.t :body, user_name: user.name
# end
#
+ # When you don't pass an explicit receiver, it executes the whole block
+ # in merging options context:
+ #
+ # class Account < ActiveRecord::Base
+ # with_options dependent: :destroy do
+ # has_many :customers
+ # has_many :products
+ # has_many :invoices
+ # has_many :expenses
+ # end
+ # end
+ #
# <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
# Each nesting level will merge inherited defaults in addition to their own.
- def with_options(options)
- yield ActiveSupport::OptionMerger.new(self, options)
+ def with_options(options, &block)
+ option_merger = ActiveSupport::OptionMerger.new(self, options)
+ block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
end
end
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
deleted file mode 100644
index ff1eb52843..0000000000
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module SecureRandom #:nodoc:
- UUID_DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
- UUID_URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
- UUID_OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
- UUID_X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
- # Generates a v5 non-random UUID (Universally Unique IDentifier).
- #
- # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
- # ::uuid_from_hash always generates the same UUID for a given name and namespace combination.
- #
- # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt
- def self.uuid_from_hash(hash_class, uuid_namespace, name) #:nodoc:
- if hash_class == Digest::MD5
- version = 3
- elsif hash_class == Digest::SHA1
- version = 5
- else
- raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
- end
-
- hash = hash_class.new
- hash.update(uuid_namespace)
- hash.update(name)
-
- ary = hash.digest.unpack('NnnnnN')
- ary[2] = (ary[2] & 0x0FFF) | (version << 12)
- ary[3] = (ary[3] & 0x3FFF) | 0x8000
-
- "%08x-%04x-%04x-%04x-%04x%08x" % ary
- end
-
- # Convenience method for ::uuid_from_hash using Digest::MD5.
- def self.uuid_v3(uuid_namespace, name) #:nodoc:
- self.uuid_from_hash(Digest::MD5, uuid_namespace, name)
- end
-
- # Convenience method for ::uuid_from_hash using Digest::SHA1.
- def self.uuid_v5(uuid_namespace, name) #:nodoc:
- self.uuid_from_hash(Digest::SHA1, uuid_namespace, name)
- end
-
- class << self
- # Alias for ::uuid.
- alias_method :uuid_v4, :uuid
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 49c0df6026..1dfaf76673 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -62,4 +62,28 @@ class String
"#{self[0, stop]}#{omission}"
end
+
+ # Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
+ #
+ # 'Once upon a time in a world far far away'.truncate_words(4)
+ # # => "Once upon a time..."
+ #
+ # Pass a string or regexp <tt>:separator</tt> to specify a different separator of words:
+ #
+ # 'Once<br>upon<br>a<br>time<br>in<br>a<br>world'.truncate_words(5, separator: '<br>')
+ # # => "Once<br>upon<br>a<br>time<br>in..."
+ #
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "..."):
+ #
+ # 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)')
+ # # => "And they found that many... (continued)"
+ def truncate_words(words_count, options = {})
+ sep = options[:separator] || /\s+/
+ sep = Regexp.escape(sep.to_s) unless Regexp === sep
+ if self =~ /\A((?:.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
+ $1 + (options[:omission] || '...')
+ else
+ dup
+ end
+ end
end
diff --git a/activesupport/lib/active_support/file_watcher.rb b/activesupport/lib/active_support/file_watcher.rb
deleted file mode 100644
index 81e63e76a7..0000000000
--- a/activesupport/lib/active_support/file_watcher.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveSupport
- class FileWatcher
- class Backend
- def initialize(path, watcher)
- @watcher = watcher
- @path = path
- end
-
- def trigger(files)
- @watcher.trigger(files)
- end
- end
-
- def initialize
- @regex_matchers = {}
- end
-
- def watch(pattern, &block)
- @regex_matchers[pattern] = block
- end
-
- def trigger(files)
- trigger_files = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
-
- files.each do |file, state|
- @regex_matchers.each do |pattern, block|
- trigger_files[block][state] << file if pattern === file
- end
- end
-
- trigger_files.each do |block, payload|
- block.call payload
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index e1eb81b8bc..3d8f2d572b 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -75,6 +75,7 @@ module ActiveSupport
hash = hash.to_hash
new(hash).tap do |new_hash|
new_hash.default = hash.default
+ new_hash.default_proc = hash.default_proc if hash.default_proc
end
end
@@ -245,11 +246,11 @@ module ActiveSupport
# Convert to a regular hash with string keys.
def to_hash
- _new_hash= {}
+ _new_hash = Hash.new(default)
each do |key, value|
- _new_hash[convert_key(key)] = convert_value(value, for: :to_hash)
+ _new_hash[key] = convert_value(value, for: :to_hash)
end
- Hash.new(default).merge!(_new_hash)
+ _new_hash
end
protected
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 23cd6716e3..affcfb7398 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -8,8 +8,6 @@ module I18n
config.i18n.railties_load_path = []
config.i18n.load_path = []
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
- # Enforce I18n to check the available locales when setting a locale.
- config.i18n.enforce_available_locales = true
# Set the i18n configuration after initialization since a lot of
# configuration is still usually done in application initializers.
@@ -36,7 +34,7 @@ module I18n
# Avoid issues with setting the default_locale by disabling available locales
# check while configuring.
enforce_available_locales = app.config.i18n.delete(:enforce_available_locales)
- enforce_available_locales = I18n.enforce_available_locales unless I18n.enforce_available_locales.nil?
+ enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
I18n.enforce_available_locales = false
app.config.i18n.each do |setting, value|
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 51720d0192..18ba79a8f9 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -348,10 +348,11 @@ module ActiveSupport
private
- # Mount a regular expression that will match part by part of the constant.
+ # Mounts a regular expression, returned as a string to ease interpolation,
+ # that will match part by part the given constant.
#
- # const_regexp("Foo::Bar::Baz") # => /Foo(::Bar(::Baz)?)?/
- # const_regexp("::") # => /::/
+ # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
+ # const_regexp("::") # => "::"
def const_regexp(camel_cased_word) #:nodoc:
parts = camel_cased_word.split("::")
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index ea3cdcd024..62caff77a3 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -213,7 +213,8 @@ module ActiveSupport
end
# Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1.
- if '<3'.respond_to?(:scrub)
+ # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars.
+ if '<3'.respond_to?(:scrub) && !defined?(Rubinius)
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
# resulting in a valid UTF-8 string.
#
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index e6c125bfdd..0df599b692 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,3 +1,5 @@
+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'
@@ -9,25 +11,15 @@ require 'active_support/testing/time_helpers'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/deprecation'
-begin
- silence_warnings { require 'mocha/setup' }
-rescue LoadError
-end
-
module ActiveSupport
class TestCase < ::Minitest::Test
Assertion = Minitest::Assertion
- alias_method :method_name, :name
-
- $tags = {}
- def self.for_tag(tag)
- yield if $tags[tag]
+ class << self
+ alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent!
end
- # FIXME: we have tests that depend on run order, we should fix that and
- # remove this method call.
- self.i_suck_and_my_tests_are_order_dependent!
+ alias_method :method_name, :name
include ActiveSupport::Testing::TaggedLogging
include ActiveSupport::Testing::SetupAndTeardown
diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb
index f4cee64091..843ce4a867 100644
--- a/activesupport/lib/active_support/testing/tagged_logging.rb
+++ b/activesupport/lib/active_support/testing/tagged_logging.rb
@@ -6,7 +6,7 @@ module ActiveSupport
attr_writer :tagged_logger
def before_setup
- if tagged_logger
+ if tagged_logger && tagged_logger.info?
heading = "#{self.class}: #{name}"
divider = '-' * heading.size
tagged_logger.info divider
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 0b393e0c7a..52fbaf8a85 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -36,3 +36,10 @@ end
def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+# FIXME: we have tests that depend on run order, we should fix that and
+# remove this method call.
+require 'active_support/test_case'
+ActiveSupport::TestCase.my_tests_are_order_dependent!
diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb
index dd67a45cf6..05580352a9 100644
--- a/activesupport/test/clean_backtrace_test.rb
+++ b/activesupport/test/clean_backtrace_test.rb
@@ -34,6 +34,11 @@ class BacktraceCleanerSilencerTest < ActiveSupport::TestCase
[ "/other/class.rb" ],
@bc.clean([ "/mongrel/class.rb", "/other/class.rb", "/mongrel/stuff.rb" ])
end
+
+ test "backtrace cleaner should allow removing silencer" do
+ @bc.remove_silencers!
+ assert_equal ["/mongrel/stuff.rb"], @bc.clean(["/mongrel/stuff.rb"])
+ end
end
class BacktraceCleanerMultipleSilencersTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb
new file mode 100644
index 0000000000..f14f64421d
--- /dev/null
+++ b/activesupport/test/core_ext/array/access_test.rb
@@ -0,0 +1,30 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class AccessTest < 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_specific_accessor
+ array = (1..42).to_a
+
+ assert_equal array[1], array.second
+ assert_equal array[2], array.third
+ assert_equal array[3], array.fourth
+ assert_equal array[4], array.fifth
+ assert_equal array[41], array.forty_two
+ end
+end
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
new file mode 100644
index 0000000000..577b889410
--- /dev/null
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -0,0 +1,197 @@
+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/string'
+
+class ToSentenceTest < ActiveSupport::TestCase
+ def test_plain_array_to_sentence
+ assert_equal "", [].to_sentence
+ assert_equal "one", ['one'].to_sentence
+ assert_equal "one and two", ['one', 'two'].to_sentence
+ assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence
+ end
+
+ def test_to_sentence_with_words_connector
+ assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(words_connector: ' ')
+ assert_equal "one & two, and three", ['one', 'two', 'three'].to_sentence(words_connector: ' & ')
+ assert_equal "onetwo, and three", ['one', 'two', 'three'].to_sentence(words_connector: nil)
+ end
+
+ def test_to_sentence_with_last_word_connector
+ assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(last_word_connector: ', and also ')
+ assert_equal "one, twothree", ['one', 'two', 'three'].to_sentence(last_word_connector: nil)
+ assert_equal "one, two three", ['one', 'two', 'three'].to_sentence(last_word_connector: ' ')
+ assert_equal "one, two and three", ['one', 'two', 'three'].to_sentence(last_word_connector: ' and ')
+ end
+
+ def test_two_elements
+ assert_equal "one and two", ['one', 'two'].to_sentence
+ assert_equal "one two", ['one', 'two'].to_sentence(two_words_connector: ' ')
+ end
+
+ def test_one_element
+ assert_equal "one", ['one'].to_sentence
+ end
+
+ def test_one_element_not_same_object
+ elements = ["one"]
+ assert_not_equal elements[0].object_id, elements.to_sentence.object_id
+ end
+
+ def test_one_non_string_element
+ assert_equal '1', [1].to_sentence
+ end
+
+ def test_does_not_modify_given_hash
+ options = { words_connector: ' ' }
+ assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options)
+ assert_equal({ words_connector: ' ' }, options)
+ end
+
+ def test_with_blank_elements
+ assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence
+ end
+
+ def test_with_invalid_options
+ exception = assert_raise ArgumentError do
+ ['one', 'two'].to_sentence(passing: 'invalid option')
+ end
+
+ assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale"
+ end
+end
+
+class ToSTest < ActiveSupport::TestCase
+ class TestDB
+ @@counter = 0
+ def id
+ @@counter += 1
+ end
+ end
+
+ def test_to_s_db
+ collection = [TestDB.new, TestDB.new, TestDB.new]
+
+ assert_equal "null", [].to_s(:db)
+ assert_equal "1,2,3", collection.to_s(:db)
+ end
+end
+
+class ToXmlTest < ActiveSupport::TestCase
+ 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') }
+ ].to_xml(skip_instruct: true, indent: 0)
+
+ assert_equal '<objects type="array"><object>', xml.first(30)
+ assert xml.include?(%(<age type="integer">26</age>)), xml
+ assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml
+ assert xml.include?(%(<name>David</name>)), xml
+ assert xml.include?(%(<age type="integer">31</age>)), xml
+ assert xml.include?(%(<age-in-millis type="decimal">1.0</age-in-millis>)), xml
+ 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 }
+ ].to_xml(skip_instruct: true, indent: 0, root: "people")
+
+ assert_equal '<people type="array"><person>', xml.first(29)
+ end
+
+ def test_to_xml_with_options
+ xml = [
+ { name: "David", street_address: "Paulina" }, { name: "Jason", street_address: "Evergreen" }
+ ].to_xml(skip_instruct: true, skip_types: true, indent: 0)
+
+ assert_equal "<objects><object>", xml.first(17)
+ assert xml.include?(%(<street-address>Paulina</street-address>))
+ assert xml.include?(%(<name>David</name>))
+ assert xml.include?(%(<street-address>Evergreen</street-address>))
+ 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" }
+ ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: false)
+
+ assert_equal "<objects><object>", xml.first(17)
+ assert xml.include?(%(<street_address>Paulina</street_address>))
+ assert xml.include?(%(<street_address>Evergreen</street_address>))
+ end
+
+ def test_to_xml_with_dasherize_true
+ xml = [
+ { name: "David", street_address: "Paulina" }, { name: "Jason", street_address: "Evergreen" }
+ ].to_xml(skip_instruct: true, skip_types: true, indent: 0, dasherize: true)
+
+ assert_equal "<objects><object>", xml.first(17)
+ assert xml.include?(%(<street-address>Paulina</street-address>))
+ assert xml.include?(%(<street-address>Evergreen</street-address>))
+ end
+
+ 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') }
+ ].to_xml(skip_instruct: false, indent: 0)
+
+ assert_match(/^<\?xml [^>]*/, xml)
+ assert_equal 0, xml.rindex(/<\?xml /)
+ end
+
+ def test_to_xml_with_block
+ xml = [
+ { name: "David", age: 26, age_in_millis: 820497600000 },
+ { name: "Jason", age: 31, age_in_millis: BigDecimal.new('1.0') }
+ ].to_xml(skip_instruct: true, indent: 0) do |builder|
+ builder.count 2
+ end
+
+ assert xml.include?(%(<count>2</count>)), xml
+ end
+
+ def test_to_xml_with_empty
+ xml = [].to_xml
+ assert_match(/type="array"\/>/, xml)
+ end
+
+ def test_to_xml_dups_options
+ options = { skip_instruct: true }
+ [].to_xml(options)
+ # :builder, etc, shouldn't be added to options
+ assert_equal({ skip_instruct: true }, options)
+ end
+end
diff --git a/activesupport/test/core_ext/array/extract_options_test.rb b/activesupport/test/core_ext/array/extract_options_test.rb
new file mode 100644
index 0000000000..0481a507cf
--- /dev/null
+++ b/activesupport/test/core_ext/array/extract_options_test.rb
@@ -0,0 +1,45 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/hash'
+
+class ExtractOptionsTest < ActiveSupport::TestCase
+ class HashSubclass < Hash
+ end
+
+ class ExtractableHashSubclass < Hash
+ def extractable_options?
+ true
+ end
+ end
+
+ def test_extract_options
+ assert_equal({}, [].extract_options!)
+ assert_equal({}, [1].extract_options!)
+ assert_equal({ a: :b }, [{ a: :b }].extract_options!)
+ assert_equal({ a: :b }, [1, { a: :b }].extract_options!)
+ end
+
+ def test_extract_options_doesnt_extract_hash_subclasses
+ hash = HashSubclass.new
+ hash[:foo] = 1
+ array = [hash]
+ options = array.extract_options!
+ assert_equal({}, options)
+ assert_equal([hash], array)
+ end
+
+ def test_extract_options_extracts_extractable_subclass
+ hash = ExtractableHashSubclass.new
+ hash[:foo] = 1
+ array = [hash]
+ options = array.extract_options!
+ assert_equal({ foo: 1 }, options)
+ assert_equal([], array)
+ end
+
+ def test_extract_options_extracts_hash_with_indifferent_access
+ array = [{ foo: 1 }.with_indifferent_access]
+ options = array.extract_options!
+ assert_equal(1, options[:foo])
+ end
+end
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
new file mode 100644
index 0000000000..2eb0f05141
--- /dev/null
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -0,0 +1,126 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class GroupingTest < ActiveSupport::TestCase
+ def setup
+ Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
+ end
+
+ def teardown
+ Fixnum.send :public, :/
+ end
+
+ def test_in_groups_of_with_perfect_fit
+ groups = []
+ ('a'..'i').to_a.in_groups_of(3) do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), %w(g h i)], groups
+ assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3)
+ end
+
+ def test_in_groups_of_with_padding
+ groups = []
+ ('a'..'g').to_a.in_groups_of(3) do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups
+ end
+
+ def test_in_groups_of_pads_with_specified_values
+ groups = []
+
+ ('a'..'g').to_a.in_groups_of(3, 'foo') do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), %w(g foo foo)], groups
+ end
+
+ def test_in_groups_of_without_padding
+ groups = []
+
+ ('a'..'g').to_a.in_groups_of(3, false) do |group|
+ groups << group
+ end
+
+ assert_equal [%w(a b c), %w(d e f), %w(g)], groups
+ end
+
+ def test_in_groups_returned_array_size
+ array = (1..7).to_a
+
+ 1.upto(array.size + 1) do |number|
+ assert_equal number, array.in_groups(number).size
+ end
+ end
+
+ def test_in_groups_with_empty_array
+ assert_equal [[], [], []], [].in_groups(3)
+ end
+
+ def test_in_groups_with_block
+ array = (1..9).to_a
+ groups = []
+
+ array.in_groups(3) do |group|
+ groups << group
+ end
+
+ assert_equal array.in_groups(3), groups
+ end
+
+ def test_in_groups_with_perfect_fit
+ assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ (1..9).to_a.in_groups(3)
+ end
+
+ def test_in_groups_with_padding
+ array = (1..7).to_a
+
+ assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]],
+ array.in_groups(3)
+ assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']],
+ array.in_groups(3, 'foo')
+ end
+
+ def test_in_groups_without_padding
+ assert_equal [[1, 2, 3], [4, 5], [6, 7]],
+ (1..7).to_a.in_groups(3, false)
+ end
+
+ def test_in_groups_invalid_argument
+ assert_raises(ArgumentError) { [].in_groups_of(0) }
+ assert_raises(ArgumentError) { [].in_groups_of(-1) }
+ assert_raises(ArgumentError) { [].in_groups_of(nil) }
+ end
+end
+
+class SplitTest < ActiveSupport::TestCase
+ def test_split_with_empty_array
+ assert_equal [[]], [].split(0)
+ end
+
+ def test_split_with_argument
+ a = [1, 2, 3, 4, 5]
+ assert_equal [[1, 2], [4, 5]], a.split(3)
+ assert_equal [[1, 2, 3, 4, 5]], a.split(0)
+ assert_equal [1, 2, 3, 4, 5], a
+ end
+
+ def test_split_with_block
+ a = (1..10).to_a
+ assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 }
+ assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 ,10], a
+ end
+
+ def test_split_with_edge_values
+ a = [1, 2, 3, 4, 5]
+ assert_equal [[], [2, 3, 4, 5]], a.split(1)
+ assert_equal [[1, 2, 3, 4], []], a.split(5)
+ assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 }
+ assert_equal [1, 2, 3, 4, 5], a
+ end
+end
diff --git a/activesupport/test/core_ext/array/prepend_append_test.rb b/activesupport/test/core_ext/array/prepend_append_test.rb
new file mode 100644
index 0000000000..762aa69b2b
--- /dev/null
+++ b/activesupport/test/core_ext/array/prepend_append_test.rb
@@ -0,0 +1,12 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class PrependAppendTest < ActiveSupport::TestCase
+ def test_append
+ assert_equal [1, 2], [1].append(2)
+ end
+
+ def test_prepend
+ assert_equal [2, 1], [1].prepend(2)
+ end
+end
diff --git a/activesupport/test/core_ext/array/wrap_test.rb b/activesupport/test/core_ext/array/wrap_test.rb
new file mode 100644
index 0000000000..baf426506f
--- /dev/null
+++ b/activesupport/test/core_ext/array/wrap_test.rb
@@ -0,0 +1,77 @@
+require 'abstract_unit'
+require 'active_support/core_ext/array'
+
+class WrapTest < ActiveSupport::TestCase
+ class FakeCollection
+ def to_ary
+ ["foo", "bar"]
+ end
+ end
+
+ class Proxy
+ def initialize(target) @target = target end
+ def method_missing(*a) @target.send(*a) end
+ end
+
+ class DoubtfulToAry
+ def to_ary
+ :not_an_array
+ end
+ end
+
+ class NilToAry
+ def to_ary
+ nil
+ end
+ end
+
+ def test_array
+ ary = %w(foo bar)
+ assert_same ary, Array.wrap(ary)
+ end
+
+ def test_nil
+ assert_equal [], Array.wrap(nil)
+ end
+
+ def test_object
+ o = Object.new
+ assert_equal [o], Array.wrap(o)
+ end
+
+ def test_string
+ assert_equal ["foo"], Array.wrap("foo")
+ end
+
+ def test_string_with_newline
+ assert_equal ["foo\nbar"], Array.wrap("foo\nbar")
+ end
+
+ def test_object_with_to_ary
+ assert_equal ["foo", "bar"], Array.wrap(FakeCollection.new)
+ end
+
+ def test_proxy_object
+ p = Proxy.new(Object.new)
+ assert_equal [p], Array.wrap(p)
+ end
+
+ def test_proxy_to_object_with_to_ary
+ p = Proxy.new(FakeCollection.new)
+ assert_equal [p], Array.wrap(p)
+ end
+
+ def test_struct
+ o = Struct.new(:foo).new(123)
+ assert_equal [o], Array.wrap(o)
+ end
+
+ def test_wrap_returns_wrapped_if_to_ary_returns_nil
+ o = NilToAry.new
+ assert_equal [o], Array.wrap(o)
+ end
+
+ def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array
+ assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new)
+ end
+end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
deleted file mode 100644
index bd1b818717..0000000000
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ /dev/null
@@ -1,482 +0,0 @@
-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/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
- array = (1..42).to_a
-
- assert_equal array[1], array.second
- assert_equal array[2], array.third
- assert_equal array[3], array.fourth
- assert_equal array[4], array.fifth
- assert_equal array[41], array.forty_two
- end
-end
-
-class ArrayExtToParamTests < ActiveSupport::TestCase
- class ToParam < String
- def to_param
- "#{self}1"
- end
- end
-
- def test_string_array
- assert_equal '', %w().to_param
- assert_equal 'hello/world', %w(hello world).to_param
- assert_equal 'hello/10', %w(hello 10).to_param
- end
-
- def test_number_array
- assert_equal '10/20', [10, 20].to_param
- end
-
- def test_to_param_array
- assert_equal 'custom1/param1', [ToParam.new('custom'), ToParam.new('param')].to_param
- end
-end
-
-class ArrayExtToSentenceTests < ActiveSupport::TestCase
- def test_plain_array_to_sentence
- assert_equal "", [].to_sentence
- assert_equal "one", ['one'].to_sentence
- assert_equal "one and two", ['one', 'two'].to_sentence
- assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence
- end
-
- def test_to_sentence_with_words_connector
- assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' ')
- assert_equal "one & two, and three", ['one', 'two', 'three'].to_sentence(:words_connector => ' & ')
- assert_equal "onetwo, and three", ['one', 'two', 'three'].to_sentence(:words_connector => nil)
- end
-
- def test_to_sentence_with_last_word_connector
- assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ', and also ')
- assert_equal "one, twothree", ['one', 'two', 'three'].to_sentence(:last_word_connector => nil)
- assert_equal "one, two three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' ')
- assert_equal "one, two and three", ['one', 'two', 'three'].to_sentence(:last_word_connector => ' and ')
- end
-
- def test_two_elements
- assert_equal "one and two", ['one', 'two'].to_sentence
- assert_equal "one two", ['one', 'two'].to_sentence(:two_words_connector => ' ')
- end
-
- def test_one_element
- assert_equal "one", ['one'].to_sentence
- end
-
- def test_one_element_not_same_object
- elements = ["one"]
- assert_not_equal elements[0].object_id, elements.to_sentence.object_id
- end
-
- def test_one_non_string_element
- assert_equal '1', [1].to_sentence
- end
-
- def test_does_not_modify_given_hash
- options = { words_connector: ' ' }
- assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options)
- assert_equal({ words_connector: ' ' }, options)
- end
-
- def test_with_blank_elements
- assert_equal ", one, , two, and three", [nil, 'one', '', 'two', 'three'].to_sentence
- end
-end
-
-class ArrayExtToSTests < ActiveSupport::TestCase
- def test_to_s_db
- collection = [
- Class.new { def id() 1 end }.new,
- Class.new { def id() 2 end }.new,
- Class.new { def id() 3 end }.new
- ]
-
- assert_equal "null", [].to_s(:db)
- assert_equal "1,2,3", collection.to_s(:db)
- end
-end
-
-class ArrayExtGroupingTests < ActiveSupport::TestCase
- def setup
- Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
- end
-
- def teardown
- Fixnum.send :public, :/
- end
-
- def test_in_groups_of_with_perfect_fit
- groups = []
- ('a'..'i').to_a.in_groups_of(3) do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), %w(g h i)], groups
- assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3)
- end
-
- def test_in_groups_of_with_padding
- groups = []
- ('a'..'g').to_a.in_groups_of(3) do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups
- end
-
- def test_in_groups_of_pads_with_specified_values
- groups = []
-
- ('a'..'g').to_a.in_groups_of(3, 'foo') do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), ['g', 'foo', 'foo']], groups
- end
-
- def test_in_groups_of_without_padding
- groups = []
-
- ('a'..'g').to_a.in_groups_of(3, false) do |group|
- groups << group
- end
-
- assert_equal [%w(a b c), %w(d e f), ['g']], groups
- end
-
- def test_in_groups_returned_array_size
- array = (1..7).to_a
-
- 1.upto(array.size + 1) do |number|
- assert_equal number, array.in_groups(number).size
- end
- end
-
- def test_in_groups_with_empty_array
- assert_equal [[], [], []], [].in_groups(3)
- end
-
- def test_in_groups_with_block
- array = (1..9).to_a
- groups = []
-
- array.in_groups(3) do |group|
- groups << group
- end
-
- assert_equal array.in_groups(3), groups
- end
-
- def test_in_groups_with_perfect_fit
- assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
- (1..9).to_a.in_groups(3)
- end
-
- def test_in_groups_with_padding
- array = (1..7).to_a
-
- assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]],
- array.in_groups(3)
- assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']],
- array.in_groups(3, 'foo')
- end
-
- def test_in_groups_without_padding
- assert_equal [[1, 2, 3], [4, 5], [6, 7]],
- (1..7).to_a.in_groups(3, false)
- end
-end
-
-class ArraySplitTests < ActiveSupport::TestCase
- def test_split_with_empty_array
- assert_equal [[]], [].split(0)
- end
-
- def test_split_with_argument
- a = [1, 2, 3, 4, 5]
- assert_equal [[1, 2], [4, 5]], a.split(3)
- assert_equal [[1, 2, 3, 4, 5]], a.split(0)
- assert_equal [1, 2, 3, 4, 5], a
- end
-
- def test_split_with_block
- a = (1..10).to_a
- assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 }
- assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 ,10], a
- end
-
- def test_split_with_edge_values
- a = [1, 2, 3, 4, 5]
- assert_equal [[], [2, 3, 4, 5]], a.split(1)
- assert_equal [[1, 2, 3, 4], []], a.split(5)
- assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 }
- assert_equal [1, 2, 3, 4, 5], a
- end
-end
-
-class ArrayToXmlTests < ActiveSupport::TestCase
- 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') }
- ].to_xml(:skip_instruct => true, :indent => 0)
-
- assert_equal '<objects type="array"><object>', xml.first(30)
- assert xml.include?(%(<age type="integer">26</age>)), xml
- assert xml.include?(%(<age-in-millis type="integer">820497600000</age-in-millis>)), xml
- assert xml.include?(%(<name>David</name>)), xml
- assert xml.include?(%(<age type="integer">31</age>)), xml
- assert xml.include?(%(<age-in-millis type="decimal">1.0</age-in-millis>)), xml
- 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 }
- ].to_xml(:skip_instruct => true, :indent => 0, :root => "people")
-
- assert_equal '<people type="array"><person>', xml.first(29)
- end
-
- def test_to_xml_with_options
- xml = [
- { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
- ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0)
-
- assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street-address>Paulina</street-address>))
- assert xml.include?(%(<name>David</name>))
- assert xml.include?(%(<street-address>Evergreen</street-address>))
- 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" }
- ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => false)
-
- assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street_address>Paulina</street_address>))
- assert xml.include?(%(<street_address>Evergreen</street_address>))
- end
-
- def test_to_xml_with_dasherize_true
- xml = [
- { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" }
- ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => true)
-
- assert_equal "<objects><object>", xml.first(17)
- assert xml.include?(%(<street-address>Paulina</street-address>))
- assert xml.include?(%(<street-address>Evergreen</street-address>))
- end
-
- 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') }
- ].to_xml(:skip_instruct => false, :indent => 0)
-
- assert_match(/^<\?xml [^>]*/, xml)
- assert_equal 0, xml.rindex(/<\?xml /)
- end
-
- def test_to_xml_with_block
- xml = [
- { :name => "David", :age => 26, :age_in_millis => 820497600000 },
- { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') }
- ].to_xml(:skip_instruct => true, :indent => 0) do |builder|
- builder.count 2
- end
-
- assert xml.include?(%(<count>2</count>)), xml
- end
-
- def test_to_xml_with_empty
- xml = [].to_xml
- assert_match(/type="array"\/>/, xml)
- end
-
- def test_to_xml_dups_options
- options = {:skip_instruct => true}
- [].to_xml(options)
- # :builder, etc, shouldn't be added to options
- assert_equal({:skip_instruct => true}, options)
- end
-end
-
-class ArrayExtractOptionsTests < ActiveSupport::TestCase
- class HashSubclass < Hash
- end
-
- class ExtractableHashSubclass < Hash
- def extractable_options?
- true
- end
- end
-
- def test_extract_options
- assert_equal({}, [].extract_options!)
- assert_equal({}, [1].extract_options!)
- assert_equal({:a=>:b}, [{:a=>:b}].extract_options!)
- assert_equal({:a=>:b}, [1, {:a=>:b}].extract_options!)
- end
-
- def test_extract_options_doesnt_extract_hash_subclasses
- hash = HashSubclass.new
- hash[:foo] = 1
- array = [hash]
- options = array.extract_options!
- assert_equal({}, options)
- assert_equal [hash], array
- end
-
- def test_extract_options_extracts_extractable_subclass
- hash = ExtractableHashSubclass.new
- hash[:foo] = 1
- array = [hash]
- options = array.extract_options!
- assert_equal({:foo => 1}, options)
- assert_equal [], array
- end
-
- def test_extract_options_extracts_hwia
- hash = [{:foo => 1}.with_indifferent_access]
- options = hash.extract_options!
- assert_equal 1, options[:foo]
- end
-end
-
-class ArrayWrapperTests < ActiveSupport::TestCase
- class FakeCollection
- def to_ary
- ["foo", "bar"]
- end
- end
-
- class Proxy
- def initialize(target) @target = target end
- def method_missing(*a) @target.send(*a) end
- end
-
- class DoubtfulToAry
- def to_ary
- :not_an_array
- end
- end
-
- class NilToAry
- def to_ary
- nil
- end
- end
-
- def test_array
- ary = %w(foo bar)
- assert_same ary, Array.wrap(ary)
- end
-
- def test_nil
- assert_equal [], Array.wrap(nil)
- end
-
- def test_object
- o = Object.new
- assert_equal [o], Array.wrap(o)
- end
-
- def test_string
- assert_equal ["foo"], Array.wrap("foo")
- end
-
- def test_string_with_newline
- assert_equal ["foo\nbar"], Array.wrap("foo\nbar")
- end
-
- def test_object_with_to_ary
- assert_equal ["foo", "bar"], Array.wrap(FakeCollection.new)
- end
-
- def test_proxy_object
- p = Proxy.new(Object.new)
- assert_equal [p], Array.wrap(p)
- end
-
- def test_proxy_to_object_with_to_ary
- p = Proxy.new(FakeCollection.new)
- assert_equal [p], Array.wrap(p)
- end
-
- def test_struct
- o = Struct.new(:foo).new(123)
- assert_equal [o], Array.wrap(o)
- end
-
- def test_wrap_returns_wrapped_if_to_ary_returns_nil
- o = NilToAry.new
- assert_equal [o], Array.wrap(o)
- end
-
- def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array
- assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new)
- end
-end
-
-class ArrayPrependAppendTest < ActiveSupport::TestCase
- def test_append
- assert_equal [1, 2], [1].append(2)
- end
-
- def test_prepend
- assert_equal [2, 1], [1].prepend(2)
- end
-end
diff --git a/activesupport/test/core_ext/digest/uuid_test.rb b/activesupport/test/core_ext/digest/uuid_test.rb
new file mode 100644
index 0000000000..08e0a1d6e1
--- /dev/null
+++ b/activesupport/test/core_ext/digest/uuid_test.rb
@@ -0,0 +1,24 @@
+require 'abstract_unit'
+require 'active_support/core_ext/digest/uuid'
+
+class DigestUUIDExt < ActiveSupport::TestCase
+ def test_v3_uuids
+ assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", Digest::UUID.uuid_v3(Digest::UUID::DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", Digest::UUID.uuid_v3(Digest::UUID::URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", Digest::UUID.uuid_v3(Digest::UUID::OID_NAMESPACE, "1.2.3")
+ assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", Digest::UUID.uuid_v3(Digest::UUID::X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_v5_uuids
+ assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", Digest::UUID.uuid_v5(Digest::UUID::DNS_NAMESPACE, "www.widgets.com")
+ assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", Digest::UUID.uuid_v5(Digest::UUID::URL_NAMESPACE, "http://www.widgets.com")
+ assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, "1.2.3")
+ assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", Digest::UUID.uuid_v5(Digest::UUID::X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
+ end
+
+ def test_invalid_hash_class
+ assert_raise ArgumentError do
+ Digest::UUID.uuid_from_hash(Digest::SHA2, Digest::UUID::OID_NAMESPACE, '1.2.3')
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 328521bdb5..31af3c4521 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -162,6 +162,10 @@ class DurationTest < ActiveSupport::TestCase
assert_equal counter, 60
end
+ def test_as_json
+ assert_equal 172800, 2.days.as_json
+ end
+
def test_to_json
assert_equal '172800', 2.days.to_json
end
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
new file mode 100644
index 0000000000..a7e12117f3
--- /dev/null
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -0,0 +1,32 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/keys'
+
+class TransformKeysTest < ActiveSupport::TestCase
+ test "transform_keys returns a new hash with the keys computed from the block" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_keys { |k| "#{k}!".to_sym }
+
+ assert_equal({ a: 'a', b: 'b' }, original)
+ assert_equal({ a!: 'a', b!: 'b' }, mapped)
+ end
+
+ test "transform_keys! modifies the keys of the original" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_keys! { |k| "#{k}!".to_sym }
+
+ assert_equal({ a!: 'a', b!: 'b' }, original)
+ assert_same original, mapped
+ end
+
+ test "transform_keys returns an Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_keys
+ assert_equal Enumerator, enumerator.class
+ end
+
+ test "transform_keys is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_keys.with_index { |k, i| [k, i].join.to_sym }
+ assert_equal({ a0: 'a', b1: 'b' }, mapped)
+ end
+end
diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb
index 4449c94701..45ed11fef7 100644
--- a/activesupport/test/core_ext/hash/transform_values_test.rb
+++ b/activesupport/test/core_ext/hash/transform_values_test.rb
@@ -46,4 +46,16 @@ class TransformValuesTest < ActiveSupport::TestCase
assert_equal 'a!', mapped[:a]
assert_nil mapped[:b]
end
+
+ test "transform_values returns an Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_values
+ assert_equal Enumerator, enumerator.class
+ end
+
+ test "transform_values is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ mapped = original.transform_values.with_index { |v, i| [v, i].join }
+ assert_equal({ a: 'a0', b: 'b1' }, mapped)
+ end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index eb8e87cc31..5e9fdfd872 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -586,6 +586,8 @@ class HashExtTest < ActiveSupport::TestCase
roundtrip = mixed_with_default.with_indifferent_access.to_hash
assert_equal @strings, roundtrip
assert_equal '1234', roundtrip.default
+
+ # Ensure nested hashes are not HashWithIndiffereneAccess
new_to_hash = @nested_mixed.with_indifferent_access.to_hash
assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
@@ -706,7 +708,7 @@ class HashExtTest < ActiveSupport::TestCase
{ :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny)
end
assert_equal "Unknown key: :failore. Valid keys are: :failure, :funny", exception.message
-
+
exception = assert_raise ArgumentError do
{ :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure ])
end
@@ -1500,12 +1502,32 @@ class HashToXmlTest < ActiveSupport::TestCase
end
end
+ def test_from_xml_array_one
+ expected = { 'numbers' => { 'type' => 'Array', 'value' => '1' }}
+ assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value></numbers>')
+ end
+
+ def test_from_xml_array_many
+ expected = { 'numbers' => { 'type' => 'Array', 'value' => [ '1', '2' ] }}
+ assert_equal expected, Hash.from_xml('<numbers type="Array"><value>1</value><value>2</value></numbers>')
+ end
+
def test_from_trusted_xml_allows_symbol_and_yaml_types
expected = { 'product' => { 'name' => :value }}
assert_equal expected, Hash.from_trusted_xml('<product><name type="symbol">value</name></product>')
assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
end
+ def test_should_use_default_proc_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_use_default_proc_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_equal 3, hash_wia.default
+ end
+
def test_should_use_default_value_for_unknown_key
hash_wia = HashWithIndifferentAccess.new(3)
assert_equal 3, hash_wia[:new_key]
@@ -1533,6 +1555,17 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal 3, hash_wia.default
end
+ def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new do
+ 2 + 1
+ end
+ assert_equal 3, hash[:foo]
+
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia[:foo]
+ assert_equal 3, hash_wia[:bar]
+ end
+
# The XML builder seems to fail miserably when trying to tag something
# with the same name as a Kernel method (throw, test, loop, select ...)
def test_kernel_method_names_to_xml
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index d8bf81d02b..a87af0007c 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -30,14 +30,6 @@ class KernelTest < ActiveSupport::TestCase
end
- def test_silence_stderr
- old_stderr_position = STDERR.tell
- silence_stderr { STDERR.puts 'hello world' }
- assert_equal old_stderr_position, STDERR.tell
- rescue Errno::ESPIPE
- # Skip if we can't STDERR.tell
- end
-
def test_silence_stream
old_stream_position = STDOUT.tell
silence_stream(STDOUT) { STDOUT.puts 'hello world' }
@@ -56,9 +48,11 @@ class KernelTest < ActiveSupport::TestCase
def test_quietly
old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell
- quietly do
- puts 'see me, feel me'
- STDERR.puts 'touch me, heal me'
+ assert_deprecated do
+ quietly do
+ puts 'see me, feel me'
+ STDERR.puts 'touch me, heal me'
+ end
end
assert_equal old_stdout_position, STDOUT.tell
assert_equal old_stderr_position, STDERR.tell
@@ -66,10 +60,6 @@ class KernelTest < ActiveSupport::TestCase
# Skip if we can't STDERR.tell
end
- def test_silence_stderr_with_return_value
- assert_equal 1, silence_stderr { 1 }
- end
-
def test_class_eval
o = Object.new
class << o; @x = 1; end
@@ -77,10 +67,18 @@ class KernelTest < ActiveSupport::TestCase
end
def test_capture
- assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' }
- assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' }
- assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') }
- assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') }
+ assert_deprecated do
+ assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' }
+ end
+ assert_deprecated do
+ assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' }
+ end
+ assert_deprecated do
+ assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') }
+ end
+ assert_deprecated do
+ assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') }
+ end
end
end
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index 31863d0aca..5f804c749b 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -14,6 +14,15 @@ class TestMissingSourceFile < ActiveSupport::TestCase
assert_equal 'nor/this/one.rb', e.path
end
end
+
+ def test_is_missing
+ begin load 'nor_does_this_one'
+ rescue MissingSourceFile => e
+ assert e.is_missing?('nor_does_this_one')
+ assert e.is_missing?('nor_does_this_one.rb')
+ assert_not e.is_missing?('some_other_file')
+ end
+ end
end
class TestLoadError < ActiveSupport::TestCase
@@ -29,4 +38,4 @@ class TestLoadError < ActiveSupport::TestCase
assert_equal 'nor/this/one.rb', e.path
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/object/acts_like_test.rb b/activesupport/test/core_ext/object/acts_like_test.rb
new file mode 100644
index 0000000000..e68b1d23cb
--- /dev/null
+++ b/activesupport/test/core_ext/object/acts_like_test.rb
@@ -0,0 +1,33 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class ObjectTests < ActiveSupport::TestCase
+ class DuckTime
+ def acts_like_time?
+ true
+ end
+ end
+
+ def test_duck_typing
+ object = Object.new
+ time = Time.now
+ date = Date.today
+ dt = DateTime.new
+ duck = DuckTime.new
+
+ assert !object.acts_like?(:time)
+ assert !object.acts_like?(:date)
+
+ assert time.acts_like?(:time)
+ assert !time.acts_like?(:date)
+
+ assert !date.acts_like?(:time)
+ assert date.acts_like?(:date)
+
+ assert dt.acts_like?(:time)
+ assert dt.acts_like?(:date)
+
+ assert duck.acts_like?(:time)
+ assert !duck.acts_like?(:date)
+ end
+end
diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb
new file mode 100644
index 0000000000..9f4c5dc4f1
--- /dev/null
+++ b/activesupport/test/core_ext/object/instance_variables_test.rb
@@ -0,0 +1,31 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class ObjectInstanceVariableTest < ActiveSupport::TestCase
+ def setup
+ @source, @dest = Object.new, Object.new
+ @source.instance_variable_set(:@bar, 'bar')
+ @source.instance_variable_set(:@baz, 'baz')
+ end
+
+ def test_instance_variable_names
+ assert_equal %w(@bar @baz), @source.instance_variable_names.sort
+ end
+
+ def test_instance_values
+ assert_equal({'bar' => 'bar', 'baz' => 'baz'}, @source.instance_values)
+ end
+
+ def test_instance_exec_passes_arguments_to_block
+ assert_equal %w(hello goodbye), 'hello'.instance_exec('goodbye') { |v| [self, v] }
+ end
+
+ def test_instance_exec_with_frozen_obj
+ assert_equal %w(olleh goodbye), 'hello'.freeze.instance_exec('goodbye') { |v| [reverse, v] }
+ end
+
+ def test_instance_exec_nested
+ assert_equal %w(goodbye olleh bar), 'hello'.instance_exec('goodbye') { |arg|
+ [arg] + instance_exec('bar') { |v| [reverse, v] } }
+ end
+end
diff --git a/activesupport/test/core_ext/object/itself_test.rb b/activesupport/test/core_ext/object/itself_test.rb
new file mode 100644
index 0000000000..65db0ddf40
--- /dev/null
+++ b/activesupport/test/core_ext/object/itself_test.rb
@@ -0,0 +1,9 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class Object::ItselfTest < ActiveSupport::TestCase
+ test 'itself returns self' do
+ object = 'fun'
+ assert_equal object, object.itself
+ end
+end
diff --git a/activesupport/test/core_ext/object/json_cherry_pick_test.rb b/activesupport/test/core_ext/object/json_cherry_pick_test.rb
new file mode 100644
index 0000000000..2f7ea3a497
--- /dev/null
+++ b/activesupport/test/core_ext/object/json_cherry_pick_test.rb
@@ -0,0 +1,42 @@
+require 'abstract_unit'
+
+# These test cases were added to test that cherry-picking the json extensions
+# works correctly, primarily for dependencies problems reported in #16131. They
+# need to be executed in isolation to reproduce the scenario correctly, because
+# other test cases might have already loaded additional dependencies.
+
+class JsonCherryPickTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def test_time_as_json
+ require_or_skip 'active_support/core_ext/object/json'
+
+ expected = Time.new(2004, 7, 25)
+ actual = Time.parse(expected.as_json)
+
+ assert_equal expected, actual
+ end
+
+ def test_date_as_json
+ require_or_skip 'active_support/core_ext/object/json'
+
+ expected = Date.new(2004, 7, 25)
+ actual = Date.parse(expected.as_json)
+
+ assert_equal expected, actual
+ end
+
+ def test_datetime_as_json
+ require_or_skip 'active_support/core_ext/object/json'
+
+ expected = DateTime.new(2004, 7, 25)
+ actual = DateTime.parse(expected.as_json)
+
+ assert_equal expected, actual
+ end
+
+ private
+ def require_or_skip(file)
+ require(file) || skip("'#{file}' was already loaded")
+ end
+end
diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb
index bd7c6c422a..30a7557dc2 100644
--- a/activesupport/test/core_ext/object/to_param_test.rb
+++ b/activesupport/test/core_ext/object/to_param_test.rb
@@ -2,6 +2,12 @@ require 'abstract_unit'
require 'active_support/core_ext/object/to_param'
class ToParamTest < ActiveSupport::TestCase
+ class CustomString < String
+ def to_param
+ "custom-#{ self }"
+ end
+ end
+
def test_object
foo = Object.new
def foo.to_s; 'foo' end
@@ -16,4 +22,16 @@ class ToParamTest < ActiveSupport::TestCase
assert_equal true, true.to_param
assert_equal false, false.to_param
end
+
+ def test_array
+ # Empty Array
+ assert_equal '', [].to_param
+
+ array = [1, 2, 3, 4]
+ assert_equal "1/2/3/4", array.to_param
+
+ # Array of different objects
+ array = [1, '3', { a: 1, b: 2 }, nil, true, false, CustomString.new('object')]
+ assert_equal "1/3/a=1&b=2//true/false/custom-object", array.to_param
+ 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 7457c4655a..09cab3ed35 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -46,6 +46,10 @@ class ToQueryTest < ActiveSupport::TestCase
:person => {:id => [20, 10]}
end
+ def test_empty_array
+ assert_equal "person%5B%5D=", [].to_query('person')
+ end
+
def test_nested_empty_hash
assert_equal '',
{}.to_query
@@ -61,6 +65,16 @@ class ToQueryTest < ActiveSupport::TestCase
{a: [], b: 3}
end
+ def test_hash_with_namespace
+ hash = { name: 'Nakshay', nationality: 'Indian' }
+ assert_equal "user%5Bname%5D=Nakshay&user%5Bnationality%5D=Indian", hash.to_query('user')
+ end
+
+ def test_hash_sorted_lexicographically
+ hash = { type: 'human', name: 'Nakshay' }
+ assert_equal "name=Nakshay&type=human", hash.to_query
+ end
+
private
def assert_query_equal(expected, actual)
assert_equal expected.split('&'), actual.to_query.split('&')
diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object/try_test.rb
index 0f454fdd95..8b754ced53 100644
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -1,70 +1,5 @@
require 'abstract_unit'
-require 'active_support/time'
require 'active_support/core_ext/object'
-require 'active_support/core_ext/class/subclasses'
-
-class ObjectTests < ActiveSupport::TestCase
- class DuckTime
- def acts_like_time?
- true
- end
- end
-
- def test_duck_typing
- object = Object.new
- time = Time.now
- date = Date.today
- dt = DateTime.new
- duck = DuckTime.new
-
- assert !object.acts_like?(:time)
- assert !object.acts_like?(:date)
-
- assert time.acts_like?(:time)
- assert !time.acts_like?(:date)
-
- assert !date.acts_like?(:time)
- assert date.acts_like?(:date)
-
- assert dt.acts_like?(:time)
- assert dt.acts_like?(:date)
-
- assert duck.acts_like?(:time)
- assert !duck.acts_like?(:date)
- end
-end
-
-class ObjectInstanceVariableTest < ActiveSupport::TestCase
- def setup
- @source, @dest = Object.new, Object.new
- @source.instance_variable_set(:@bar, 'bar')
- @source.instance_variable_set(:@baz, 'baz')
- end
-
- def test_instance_variable_names
- assert_equal %w(@bar @baz), @source.instance_variable_names.sort
- end
-
- def test_instance_values
- object = Object.new
- object.instance_variable_set :@a, 1
- object.instance_variable_set :@b, 2
- assert_equal({'a' => 1, 'b' => 2}, object.instance_values)
- end
-
- def test_instance_exec_passes_arguments_to_block
- assert_equal %w(hello goodbye), 'hello'.instance_exec('goodbye') { |v| [self, v] }
- end
-
- def test_instance_exec_with_frozen_obj
- assert_equal %w(olleh goodbye), 'hello'.freeze.instance_exec('goodbye') { |v| [reverse, v] }
- end
-
- def test_instance_exec_nested
- assert_equal %w(goodbye olleh bar), 'hello'.instance_exec('goodbye') { |arg|
- [arg] + instance_exec('bar') { |v| [reverse, v] } }
- end
-end
class ObjectTryTest < ActiveSupport::TestCase
def setup
@@ -141,7 +76,7 @@ class ObjectTryTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { klass.new.try!(:private_method) }
end
-
+
def test_try_with_private_method
klass = Class.new do
private
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 150e6b65fb..98c4ec6b5e 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -12,10 +12,11 @@ class RangeTest < ActiveSupport::TestCase
date_range = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
-
+
def test_date_range
assert_instance_of Range, DateTime.new..DateTime.new
assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new
+ assert_instance_of Range, DateTime.new..DateTime::Infinity.new
end
def test_overlaps_last_inclusive
@@ -116,4 +117,9 @@ class RangeTest < ActiveSupport::TestCase
datetime = DateTime.now
assert ((datetime - 1.hour)..datetime).each {}
end
+
+ def test_date_time_with_step
+ datetime = DateTime.now
+ assert ((datetime - 1.hour)..datetime).step(1) {}
+ end
end
diff --git a/activesupport/test/core_ext/securerandom_test.rb b/activesupport/test/core_ext/securerandom_test.rb
deleted file mode 100644
index 71980f6910..0000000000
--- a/activesupport/test/core_ext/securerandom_test.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/securerandom'
-
-class SecureRandomExt < ActiveSupport::TestCase
- def test_v3_uuids
- assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", SecureRandom.uuid_v3(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com")
- assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", SecureRandom.uuid_v3(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com")
- assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", SecureRandom.uuid_v3(SecureRandom::UUID_OID_NAMESPACE, "1.2.3")
- assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", SecureRandom.uuid_v3(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
- end
-
- def test_v5_uuids
- assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", SecureRandom.uuid_v5(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com")
- assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", SecureRandom.uuid_v5(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com")
- assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, "1.2.3")
- assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", SecureRandom.uuid_v5(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US")
- end
-
- def test_uuid_v4_alias
- assert_equal SecureRandom.method(:uuid_v4), SecureRandom.method(:uuid)
- end
-
- def test_invalid_hash_class
- assert_raise ArgumentError do
- SecureRandom.uuid_from_hash(Digest::SHA2, SecureRandom::UUID_OID_NAMESPACE, '1.2.3')
- end
- end
-end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 515144245a..d77e6be595 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -216,19 +216,40 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal "Hello Wor...", "Hello World!!".truncate(12)
end
- def test_truncate_with_omission_and_seperator
+ def test_truncate_with_omission_and_separator
assert_equal "Hello[...]", "Hello World!".truncate(10, :omission => "[...]")
assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => ' ')
assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => ' ')
assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ')
end
- def test_truncate_with_omission_and_regexp_seperator
+ def test_truncate_with_omission_and_regexp_separator
assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/)
assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/)
assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/)
end
+ def test_truncate_words
+ assert_equal "Hello Big World!", "Hello Big World!".truncate_words(3)
+ assert_equal "Hello Big...", "Hello Big World!".truncate_words(2)
+ end
+
+ def test_truncate_words_with_omission
+ assert_equal "Hello Big World!", "Hello Big World!".truncate_words(3, :omission => "[...]")
+ assert_equal "Hello Big[...]", "Hello Big World!".truncate_words(2, :omission => "[...]")
+ end
+
+ def test_truncate_words_with_separator
+ assert_equal "Hello<br>Big<br>World!...", "Hello<br>Big<br>World!<br>".truncate_words(3, :separator => '<br>')
+ assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :separator => '<br>')
+ assert_equal "Hello\n<br>Big...", "Hello\n<br>Big<br>Wide<br>World!".truncate_words(2, :separator => '<br>')
+ end
+
+ def test_truncate_words_with_separator_and_omission
+ assert_equal "Hello<br>Big<br>World![...]", "Hello<br>Big<br>World!<br>".truncate_words(3, :omission => "[...]", :separator => '<br>')
+ assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :omission => "[...]", :separator => '<br>')
+ end
+
def test_truncate_multibyte
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8),
"\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10)
@@ -754,6 +775,14 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = "<b>hello</b>".html_safe
assert_equal string, ERB::Util.html_escape(string)
end
+
+ test "ERB::Util.html_escape_once only escapes once" do
+ string = '1 < 2 &amp; 3'
+ escaped_string = "1 &lt; 2 &amp; 3"
+
+ assert_equal escaped_string, ERB::Util.html_escape_once(string)
+ assert_equal escaped_string, ERB::Util.html_escape_once(escaped_string)
+ end
end
class StringExcludeTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 75599a71c3..3000da8da4 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -367,6 +367,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_acts_like_time
+ assert @twz.acts_like_time?
assert @twz.acts_like?(:time)
assert ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:time)
end
diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb
index 03e388dd7a..43a5997ddd 100644
--- a/activesupport/test/core_ext/uri_ext_test.rb
+++ b/activesupport/test/core_ext/uri_ext_test.rb
@@ -7,7 +7,7 @@ class URIExtTest < ActiveSupport::TestCase
def test_uri_decode_handle_multibyte
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
- parser = URI::Parser.new
+ parser = URI.parser
assert_equal str, parser.unescape(parser.escape(str))
end
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index ee1c69502e..7aff56cbad 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -355,4 +355,21 @@ class DeprecationTest < ActiveSupport::TestCase
end
deprecator
end
+
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ end
end
diff --git a/activesupport/test/key_generator_test.rb b/activesupport/test/key_generator_test.rb
index 525082d313..f7e8e9a795 100644
--- a/activesupport/test/key_generator_test.rb
+++ b/activesupport/test/key_generator_test.rb
@@ -29,4 +29,34 @@ class KeyGeneratorTest < ActiveSupport::TestCase
end
end
+class CachingKeyGeneratorTest < ActiveSupport::TestCase
+ def setup
+ @secret = SecureRandom.hex(64)
+ @generator = ActiveSupport::KeyGenerator.new(@secret, :iterations=>2)
+ @caching_generator = ActiveSupport::CachingKeyGenerator.new(@generator)
+ end
+
+ test "Generating a cached key for same salt and key size" do
+ derived_key = @caching_generator.generate_key("some_salt", 32)
+ cached_key = @caching_generator.generate_key("some_salt", 32)
+
+ assert_equal derived_key, cached_key
+ assert_equal derived_key.object_id, cached_key.object_id
+ end
+
+ test "Does not cache key for different salt" do
+ derived_key = @caching_generator.generate_key("some_salt", 32)
+ different_salt_key = @caching_generator.generate_key("other_salt", 32)
+
+ assert_not_equal derived_key, different_salt_key
+ end
+
+ test "Does not cache key for different length" do
+ derived_key = @caching_generator.generate_key("some_salt", 32)
+ different_length_key = @caching_generator.generate_key("some_salt", 64)
+
+ assert_not_equal derived_key, different_length_key
+ end
+end
+
end
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index bdbdf0390a..aba81b8248 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -10,7 +10,6 @@ require 'tmpdir'
class Downloader
def self.download(from, to)
unless File.exist?(to)
- $stderr.puts "Downloading #{from} to #{to}"
unless File.exist?(File.dirname(to))
system "mkdir -p #{File.dirname(to)}"
end
@@ -22,6 +21,7 @@ class Downloader
end
end
end
+ true
end
end
@@ -31,13 +31,15 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
UNIDATA_FILE = '/NormalizationTest.txt'
CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ FileUtils.mkdir_p(CACHE_DIR)
+ RUN_P = begin
+ Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
+ rescue
+ end
def setup
- FileUtils.mkdir_p(CACHE_DIR)
- Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
@proxy = ActiveSupport::Multibyte::Chars
- rescue
- skip "Unable to download test data"
+ skip "Unable to download test data" unless RUN_P
end
def test_normalizations_C
diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb
new file mode 100644
index 0000000000..d8ffd7ca9c
--- /dev/null
+++ b/activesupport/test/multibyte_proxy_test.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+
+class MultibyteProxyText < ActiveSupport::TestCase
+ class AsciiOnlyEncoder
+ attr_reader :wrapped_string
+ alias to_s wrapped_string
+
+ def initialize(string)
+ @wrapped_string = string.gsub(/[^\u0000-\u007F]/, '?')
+ end
+ end
+
+ def with_custom_encoder(encoder)
+ original_proxy_class = ActiveSupport::Multibyte.proxy_class
+
+ begin
+ ActiveSupport::Multibyte.proxy_class = encoder
+
+ yield
+ ensure
+ ActiveSupport::Multibyte.proxy_class = original_proxy_class
+ end
+ end
+
+ test "custom multibyte encoder" do
+ with_custom_encoder(AsciiOnlyEncoder) do
+ assert_equal "s?me string 123", "søme string 123".mb_chars.to_s
+ end
+
+ assert_equal "søme string 123", "søme string 123".mb_chars.to_s
+ end
+end
diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb
index 9d139b61b8..4c0364e68b 100644
--- a/activesupport/test/option_merger_test.rb
+++ b/activesupport/test/option_merger_test.rb
@@ -79,6 +79,15 @@ class OptionMergerTest < ActiveSupport::TestCase
assert_equal ActiveSupport::OptionMerger, ActiveSupport::OptionMerger.new('', '').class
end
+ def test_option_merger_implicit_receiver
+ @options.with_options foo: "bar" do
+ merge! fizz: "buzz"
+ end
+
+ expected = { hello: "world", foo: "bar", fizz: "buzz" }
+ assert_equal expected, @options
+ end
+
private
def method_with_options(options = {})
options
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
index 21e4ba0cee..a88d8d9eba 100644
--- a/activesupport/test/subscriber_test.rb
+++ b/activesupport/test/subscriber_test.rb
@@ -49,6 +49,6 @@ class SubscriberTest < ActiveSupport::TestCase
def test_does_not_attach_private_methods
ActiveSupport::Notifications.instrument("private_party.doodle")
- assert_equal TestSubscriber.events, []
+ assert_equal [], TestSubscriber.events
end
end
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index a027d6b169..20c64b4a85 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -3,6 +3,8 @@ unless File.exist?('Gemfile')
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
+ gem 'rack', github: 'rack/rack'
+ gem 'i18n', github: 'svenfuchs/i18n'
GEMFILE
system 'bundle'
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index d95354e12d..e7f5d0d5ff 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -3,6 +3,8 @@ unless File.exist?('Gemfile')
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'arel', github: 'rails/arel'
+ gem 'rack', github: 'rack/rack'
+ gem 'i18n', github: 'svenfuchs/i18n'
gem 'sqlite3'
GEMFILE
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index 8c5abb54ea..a39dd9ace0 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -59,22 +59,35 @@ Please refer to the [Changelog][railties] for detailed changes.
### Removals
-* The `rails application` command has been removed without replacement.
- ([Pull Request](https://github.com/rails/rails/pull/11616))
+* The `rails application` command has been removed without replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/11616))
+
+### Deprecations
+
+* Deprecated `Rails::Rack::LogTailer` without replacement.
+ ([Commit](https://github.com/rails/rails/commit/84a13e019e93efaa8994b3f8303d635a7702dbce))
### Notable changes
-* Introduced `bin/setup` script to bootstrap an application.
- ([Pull Request](https://github.com/rails/rails/pull/15189))
+* Introduced `--skip-gems` option in the app generator to skip gems such as
+ `turbolinks` and `coffee-rails` that does not have their own specific flags.
+ ([Commit](https://github.com/rails/rails/commit/10565895805887d4faf004a6f71219da177f78b7))
+
+* 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))
-* 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 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))
+
+* Introduced an `after_bundle` callback in the Rails templates.
+ ([Pull Request](https://github.com/rails/rails/pull/16359))
-* 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
-----------
@@ -83,8 +96,8 @@ Please refer to the [Changelog][action-pack] 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:
+* Deprecated support for setting the `:to` option of a router to a symbol or a
+ string that does not contain a `#` character:
```ruby
get '/posts', to: MyRackApp => (No change necessary)
@@ -97,8 +110,17 @@ Please refer to the [Changelog][action-pack] for detailed changes.
### Notable changes
-* The `*_filter` family methods has been removed from the documentation. Their
- usage are discouraged in favor of the `*_action` family methods:
+* `render nothing: true` or rendering a `nil` body no longer add a single
+ space padding to the response body.
+ ([Pull Request](https://github.com/rails/rails/pull/14883))
+
+* Introduced the `always_permitted_parameters` option to configure which
+ parameters are permitted globally. The default value of this configuration
+ is `['controller', 'action']`.
+ ([Pull Request](https://github.com/rails/rails/pull/15933))
+
+* 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
@@ -123,22 +145,21 @@ Please refer to the [Changelog][action-pack] for detailed changes.
(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))
-* 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))
-* `*_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))
-* 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))
-* 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))
+* Added option to disable logging of CSRF failures.
+ ([Pull Request](https://github.com/rails/rails/pull/14280))
Action View
@@ -148,17 +169,21 @@ Please refer to the [Changelog][action-view] for detailed changes.
### Deprecations
-* Deprecated `AbstractController::Base.parent_prefixes`.
- Override `AbstractController::Base.local_prefixes` when you want to change
- where to find views.
- ([Pull Request](https://github.com/rails/rails/pull/15026))
+* Deprecated `AbstractController::Base.parent_prefixes`.
+ Override `AbstractController::Base.local_prefixes` when you want to change
+ where to find views.
+ ([Pull Request](https://github.com/rails/rails/pull/15026))
-* Deprecated `ActionView::Digestor#digest(name, format, finder, options = {})`,
- arguments should be passed as a hash instead.
- ([Pull Request](https://github.com/rails/rails/pull/14243))
+* Deprecated `ActionView::Digestor#digest(name, format, finder, options = {})`,
+ arguments should be passed as a hash instead.
+ ([Pull Request](https://github.com/rails/rails/pull/14243))
### Notable changes
+* The form helpers no longer generate a `<div>` element with inline CSS around
+ the hidden fields.
+ ([Pull Request](https://github.com/rails/rails/pull/14738))
+
Action Mailer
-------------
@@ -167,6 +192,10 @@ Please refer to the [Changelog][action-mailer] for detailed changes.
### Notable changes
+* Added the `show_previews` configuration option for enabling mailer previews
+ outside of the development environment.
+ ([Pull Request](https://github.com/rails/rails/pull/15970))
+
Active Record
-------------
@@ -177,91 +206,114 @@ for detailed changes.
### Removals
-* Removed deprecated method `ActiveRecord::Base.quoted_locking_column`.
- ([Pull Request](https://github.com/rails/rails/pull/15612))
+* Removed `cache_attributes` and friends. All attributes are cached.
+ ([Pull Request](https://github.com/rails/rails/pull/15429))
-* Removed deprecated `ActiveRecord::Migrator.proper_table_name`. Use the
- `proper_table_name` instance method on `ActiveRecord::Migration` instead.
- ([Pull Request](https://github.com/rails/rails/pull/15512))
+* Removed deprecated method `ActiveRecord::Base.quoted_locking_column`.
+ ([Pull Request](https://github.com/rails/rails/pull/15612))
-* Removed `cache_attributes` and friends. All attributes are cached.
- ([Pull Request](https://github.com/rails/rails/pull/15429))
+* Removed deprecated `ActiveRecord::Migrator.proper_table_name`. Use the
+ `proper_table_name` instance method on `ActiveRecord::Migration` instead.
+ ([Pull Request](https://github.com/rails/rails/pull/15512))
-* Removed 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.
- ([Pull Request](https://github.com/rails/rails/pull/15184))
+* Removed 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.
+ ([Pull Request](https://github.com/rails/rails/pull/15184))
### Deprecations
-* Deprecated returning `nil` from `column_for_attribute` when no column exists.
- It will return a null object in Rails 5.0
- ([Pull Request](https://github.com/rails/rails/pull/15878))
+* Deprecated broken support for automatic detection of counter caches on
+ `has_many :through` associations. You should instead manually specify the
+ counter cache on the `has_many` and `belongs_to` associations for the
+ through records.
+ ([Pull Request](https://github.com/rails/rails/pull/15754))
+
+* Deprecated `serialized_attributes` without replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/15704))
-* Deprecated `serialized_attributes` without replacement.
- ([Pull Request](https://github.com/rails/rails/pull/15704))
+* Deprecated returning `nil` from `column_for_attribute` when no column
+ exists. It will return a null object in Rails 5.0
+ ([Pull Request](https://github.com/rails/rails/pull/15878))
-* 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 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 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.
+* 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.
+ (e.g. `#succ` is not defined) it will raise an `ArgumentError` for ranges
+ with excluding beginnings.
([Commit](https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3))
-* Deprecated broken support for automatic detection of counter caches on
- `has_many :through` associations. You should instead manually specify the
- counter cache on the `has_many` and `belongs_to` associations for the through
- records.
- ([Pull Request](https://github.com/rails/rails/pull/15754))
-
### Notable changes
-* Added support for `#pretty_print` in `ActiveRecord::Base` objects.
- ([Pull Request](https://github.com/rails/rails/pull/15172))
+* Added a `:required` option to singular associations, which defines a
+ presence validation on the association.
+ ([Pull Request](https://github.com/rails/rails/pull/16056))
+
+* Introduced `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the
+ record is invalid.
+ ([Pull Request](https://github.com/rails/rails/pull/8639))
+
+* `ActiveRecord::Base#reload` now behaves the same as `m = Model.find(m.id)`,
+ meaning that it no longer retains the extra attributes from custom
+ `select`s.
+ ([Pull Request](https://github.com/rails/rails/pull/15866))
-* 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))
+* Introduced the `bin/rake db:purge` task to empty the database for the
+ current environment.
+ ([Commit](https://github.com/rails/rails/commit/e2f232aba15937a4b9d14bd91e0392c6d55be58d))
-* `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))
+* `ActiveRecord::Dirty` now detects in-place changes to mutable values.
+ Serialized attributes on ActiveRecord models will no longer save when
+ unchanged. This also works with other types such as string columns and json
+ columns on PostgreSQL.
+ (Pull Requests [1](https://github.com/rails/rails/pull/15674),
+ [2](https://github.com/rails/rails/pull/15786),
+ [3](https://github.com/rails/rails/pull/15788))
-* Introduced `#validate` as an alias for `#valid?`.
- ([Pull Request](https://github.com/rails/rails/pull/14456))
+* Added support for `#pretty_print` in `ActiveRecord::Base` objects.
+ ([Pull Request](https://github.com/rails/rails/pull/15172))
-* `#touch` now accepts multiple attributes to be touched at once.
- ([Pull Request](https://github.com/rails/rails/pull/14423))
+* 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))
-* 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))
+* `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))
-* Added support for the `citext` column type in PostgreSQL adapter.
- ([Pull Request](https://github.com/rails/rails/pull/12523))
+* Introduced `#validate` as an alias for `#valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
-* Added support for user-created range types in PostgreSQL adapter.
- ([Commit](https://github.com/rails/rails/commit/4cb47167e747e8f9dc12b0ddaf82bdb68c03e032))
+* `#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))
-* Added a `:required` option to singular associations, which defines a
- presence validation on the association.
- ([Pull Request](https://github.com/rails/rails/pull/16056))
Active Model
------------
@@ -270,13 +322,21 @@ Please refer to the [Changelog][active-model] for detailed changes.
### Removals
-* Removed deprecated `Validator#setup` without replacement.
- ([Pull Request](https://github.com/rails/rails/pull/15617))
+* Removed deprecated `Validator#setup` without replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/15617))
### Notable changes
-* Introduced `#validate` as an alias for `#valid?`.
- ([Pull Request](https://github.com/rails/rails/pull/14456))
+* Introduced `undo_changes` method in `ActiveModel::Dirty` to restore the
+ changed (dirty) attributes to their previous values.
+ ([Pull Request](https://github.com/rails/rails/pull/14861))
+
+* `has_secure_password` now verifies that the given password is less than 72
+ characters if validations are enabled.
+ ([Pull Request](https://github.com/rails/rails/pull/15708))
+
+* Introduced `#validate` as an alias for `#valid?`.
+ ([Pull Request](https://github.com/rails/rails/pull/14456))
Active Support
@@ -286,44 +346,46 @@ Please refer to the [Changelog][active-support] 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 `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))
+* 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 `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))
+* 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 `Hash#transform_values` and `Hash#transform_values!` to simplify a
+ common pattern where the values of a hash must change, but the keys are left
+ the same.
+ ([Pull Request](https://github.com/rails/rails/pull/15819))
-* Added `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5`.
- ([Pull Request](https://github.com/rails/rails/pull/12016))
+* The `humanize` inflector helper now strips any leading underscores.
+ ([Commit](https://github.com/rails/rails/commit/daaa21bc7d20f2e4ff451637423a25ff2d5e75c7))
-* 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))
+* 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))
-* Added `Hash#transform_values` and `Hash#transform_values!` to simplify a
- common pattern where the values of a hash must change, but the keys are left
- the same.
- ([Pull Request](https://github.com/rails/rails/pull/15819))
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.
+the many people who spent many hours making Rails the stable and robust
+framework it is today. Kudos to all of them.
[railties]: https://github.com/rails/rails/blob/4-2-stable/railties/CHANGELOG.md
[action-pack]: https://github.com/rails/rails/blob/4-2-stable/actionpack/CHANGELOG.md
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index cb1c1c653d..9ad9319255 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -414,6 +414,22 @@ globally in `config/application.rb`:
config.action_mailer.default_url_options = { host: 'example.com' }
```
+Because of this behavior you cannot use any of the `*_path` helpers inside of
+an email. Instead you will need to use the associated `*_url` helper. For example
+instead of using
+
+```
+<%= link_to 'welcome', welcome_path %>
+```
+
+You will need to use:
+
+```
+<%= link_to 'welcome', welcome_url %>
+```
+
+By using the full URL, your links will now work in your emails.
+
#### generating URLs with `url_for`
You need to pass the `only_path: false` option when using `url_for`. This will
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 1131a83c36..3eaeeff389 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -202,7 +202,7 @@ person.valid? # => raises ActiveModel::StrictValidationFa
### ActiveModel::Naming
Naming adds a number of class methods which make the naming and routing
-easier to manage. The module defines the `model_name` class method which
+easier to manage. The module defines the `model_name` class method which
will define a number of accessors using some `ActiveSupport::Inflector` methods.
```ruby
@@ -220,4 +220,4 @@ Person.model_name.param_key # => "person"
Person.model_name.i18n_key # => :person
Person.model_name.route_key # => "people"
Person.model_name.singular_route_key # => "person"
-``` \ No newline at end of file
+```
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 21022f1abb..eff93ce41d 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -310,10 +310,10 @@ 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 the database, so
-the methods `create`, `save` and `update` take it into account when
+the methods `save` and `update` take it into account when
running: they return `false` when validation fails and they didn't actually
perform any operation on the database. All of these have a bang counterpart (that
-is, `create!`, `save!` and `update!`), which are stricter in that
+is, `save!` and `update!`), which are stricter in that
they raise the exception `ActiveRecord::RecordInvalid` if validation fails.
A quick example to illustrate:
@@ -322,8 +322,9 @@ class User < ActiveRecord::Base
validates :name, presence: true
end
-User.create # => false
-User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
+user = User.new
+user.save # => false
+user.save! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
```
You can learn more about validations in the [Active Record Validations
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 486e7b80ff..35467fe95b 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -93,9 +93,9 @@ The primary operation of `Model.find(options)` can be summarized as:
Active Record provides several different ways of retrieving a single object.
-#### Using a Primary Key
+#### `find`
-Using `Model.find(primary_key)`, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example:
+Using the `find` method, you can retrieve the object corresponding to the specified _primary key_ that matches any supplied options. For example:
```ruby
# Find the client with primary key (id) 10.
@@ -109,119 +109,103 @@ The SQL equivalent of the above is:
SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
```
-`Model.find(primary_key)` will raise an `ActiveRecord::RecordNotFound` exception if no matching record is found.
+The `find` method will raise an `ActiveRecord::RecordNotFound` exception if no matching record is found.
-#### `take`
-
-`Model.take` retrieves a record without any implicit ordering. For example:
+You can also use this method to query for multiple objects. Call the `find` method and pass in an array of primary keys. The return will be an array containing all of the matching records for the supplied _primary keys_. For example:
```ruby
-client = Client.take
-# => #<Client id: 1, first_name: "Lifo">
+# Find the clients with primary keys 1 and 10.
+client = Client.find([1, 10]) # Or even Client.find(1, 10)
+# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients WHERE (clients.id IN (1,10))
```
-`Model.take` returns `nil` if no record is found and no exception will be raised.
+WARNING: The `find` method will raise an `ActiveRecord::RecordNotFound` exception unless a matching record is found for **all** of the supplied primary keys.
-TIP: The retrieved record may vary depending on the database engine.
-
-#### `first`
+#### `take`
-`Model.first` finds the first record ordered by the primary key. For example:
+The `take` method retrieves a record without any implicit ordering. For example:
```ruby
-client = Client.first
+client = Client.take
# => #<Client id: 1, first_name: "Lifo">
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
+SELECT * FROM clients LIMIT 1
```
-`Model.first` returns `nil` if no matching record is found and no exception will be raised.
-
-#### `last`
+The `take` method returns `nil` if no record is found and no exception will be raised.
-`Model.last` finds the last record ordered by the primary key. For example:
+You can pass in a numerical argument to the `take` method to return up to that number of results. For example
```ruby
-client = Client.last
-# => #<Client id: 221, first_name: "Russel">
+client = Client.take(2)
+# => [
+ #<Client id: 1, first_name: "Lifo">,
+ #<Client id: 220, first_name: "Sara">
+]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
-```
-
-`Model.last` returns `nil` if no matching record is found and no exception will be raised.
-
-#### `find_by`
-
-`Model.find_by` finds the first record matching some conditions. For example:
-
-```ruby
-Client.find_by first_name: 'Lifo'
-# => #<Client id: 1, first_name: "Lifo">
-
-Client.find_by first_name: 'Jon'
-# => nil
+SELECT * FROM clients LIMIT 2
```
-It is equivalent to writing:
+The `take!` method behaves exactly like `take`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
-```ruby
-Client.where(first_name: 'Lifo').take
-```
+TIP: The retrieved record may vary depending on the database engine.
-#### `take!`
+#### `first`
-`Model.take!` retrieves a record without any implicit ordering. For example:
+The `first` method finds the first record ordered by the primary key. For example:
```ruby
-client = Client.take!
+client = Client.first
# => #<Client id: 1, first_name: "Lifo">
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
```
-`Model.take!` raises `ActiveRecord::RecordNotFound` if no matching record is found.
-
-#### `first!`
+The `first` method returns `nil` if no matching record is found and no exception will be raised.
-`Model.first!` finds the first record ordered by the primary key. For example:
+You can pass in a numerical argument to the `first` method to return up to that number of results. For example
```ruby
-client = Client.first!
-# => #<Client id: 1, first_name: "Lifo">
+client = Client.first(3)
+# => [
+ #<Client id: 1, first_name: "Lifo">,
+ #<Client id: 2, first_name: "Fifo">,
+ #<Client id: 3, first_name: "Filo">
+]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3
```
-`Model.first!` raises `ActiveRecord::RecordNotFound` if no matching record is found.
+The `first!` method behaves exactly like `first`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
-#### `last!`
+#### `last`
-`Model.last!` finds the last record ordered by the primary key. For example:
+The `last` method finds the last record ordered by the primary key. For example:
```ruby
-client = Client.last!
+client = Client.last
# => #<Client id: 221, first_name: "Russel">
```
@@ -231,92 +215,56 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
```
-`Model.last!` raises `ActiveRecord::RecordNotFound` if no matching record is found.
-
-#### `find_by!`
-
-`Model.find_by!` finds the first record matching some conditions. It raises `ActiveRecord::RecordNotFound` if no matching record is found. For example:
-
-```ruby
-Client.find_by! first_name: 'Lifo'
-# => #<Client id: 1, first_name: "Lifo">
-
-Client.find_by! first_name: 'Jon'
-# => ActiveRecord::RecordNotFound
-```
-
-It is equivalent to writing:
-
-```ruby
-Client.where(first_name: 'Lifo').take!
-```
-
-### Retrieving Multiple Objects
+The `last` method returns `nil` if no matching record is found and no exception will be raised.
-#### Using Multiple Primary Keys
-
-`Model.find(array_of_primary_key)` accepts an array of _primary keys_, returning an array containing all of the matching records for the supplied _primary keys_. For example:
+You can pass in a numerical argument to the `last` method to return up to that number of results. For example
```ruby
-# Find the clients with primary keys 1 and 10.
-client = Client.find([1, 10]) # Or even Client.find(1, 10)
-# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
+client = Client.last(3)
+# => [
+ #<Client id: 219, first_name: "James">,
+ #<Client id: 220, first_name: "Sara">,
+ #<Client id: 221, first_name: "Russel">
+]
```
The SQL equivalent of the above is:
```sql
-SELECT * FROM clients WHERE (clients.id IN (1,10))
+SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3
```
-WARNING: `Model.find(array_of_primary_key)` will raise an `ActiveRecord::RecordNotFound` exception unless a matching record is found for **all** of the supplied primary keys.
+The `last!` method behaves exactly like `last`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
-#### take
+#### `find_by`
-`Model.take(limit)` retrieves the first number of records specified by `limit` without any explicit ordering:
+The `find_by` method finds the first record matching some conditions. For example:
```ruby
-Client.take(2)
-# => [#<Client id: 1, first_name: "Lifo">,
- #<Client id: 2, first_name: "Raf">]
-```
-
-The SQL equivalent of the above is:
+Client.find_by first_name: 'Lifo'
+# => #<Client id: 1, first_name: "Lifo">
-```sql
-SELECT * FROM clients LIMIT 2
+Client.find_by first_name: 'Jon'
+# => nil
```
-#### first
-
-`Model.first(limit)` finds the first number of records specified by `limit` ordered by primary key:
+It is equivalent to writing:
```ruby
-Client.first(2)
-# => [#<Client id: 1, first_name: "Lifo">,
- #<Client id: 2, first_name: "Raf">]
-```
-
-The SQL equivalent of the above is:
-
-```sql
-SELECT * FROM clients ORDER BY id ASC LIMIT 2
+Client.where(first_name: 'Lifo').take
```
-#### last
-
-`Model.last(limit)` finds the number of records specified by `limit` ordered by primary key in descending order:
+The `find_by!` method behaves exactly like `find_by`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found. For example:
```ruby
-Client.last(2)
-# => [#<Client id: 10, first_name: "Ryan">,
- #<Client id: 9, first_name: "John">]
+Client.find_by! first_name: 'does not exist'
+# => ActiveRecord::RecordNotFound
```
-The SQL equivalent of the above is:
+This is equivalent to writing:
-```sql
-SELECT * FROM clients ORDER BY id DESC LIMIT 2
+```ruby
+Client.where(first_name: 'does not exist').take!
```
### Retrieving Multiple Objects in Batches
@@ -328,7 +276,7 @@ This may appear straightforward:
```ruby
# This is very inefficient when the users table has thousands of rows.
User.all.each do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver
end
```
@@ -344,7 +292,15 @@ The `find_each` method retrieves a batch of records and then yields _each_ recor
```ruby
User.find_each do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver
+end
+```
+
+To add conditions to a `find_each` operation you can chain other Active Record methods such as `where`:
+
+```ruby
+User.where(weekly_subscriber: true).find_each do |user|
+ NewsMailer.weekly(user).deliver
end
```
@@ -360,7 +316,7 @@ The `:batch_size` option allows you to specify the number of records to be retri
```ruby
User.find_each(batch_size: 5000) do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver
end
```
@@ -372,7 +328,7 @@ For example, to send newsletters only to users with the primary key starting fro
```ruby
User.find_each(start: 2000, batch_size: 5000) do |user|
- NewsLetter.weekly_deliver(user)
+ NewsMailer.weekly(user).deliver
end
```
@@ -707,7 +663,7 @@ Overriding Conditions
You can specify certain conditions to be removed using the `unscope` method. For example:
```ruby
-Article.where('id > 10').limit(20).order('id asc').except(:order)
+Article.where('id > 10').limit(20).order('id asc').unscope(:order)
```
The SQL that would be executed:
@@ -720,7 +676,7 @@ SELECT * FROM articles WHERE id > 10 ORDER BY id asc LIMIT 20
```
-You can additionally unscope specific where clauses. For example:
+You can also unscope specific `where` clauses. For example:
```ruby
Article.where(id: 10, trashed: false).unscope(where: :id)
@@ -759,8 +715,6 @@ The `reorder` method overrides the default scope order. For example:
```ruby
class Article < ActiveRecord::Base
- ..
- ..
has_many :comments, -> { order('posted_at DESC') }
end
@@ -1487,6 +1441,11 @@ If you'd like to use your own SQL to find records in a table you can use `find_b
Client.find_by_sql("SELECT * FROM clients
INNER JOIN orders ON clients.id = orders.client_id
ORDER BY clients.created_at desc")
+# => [
+ #<Client id: 1, first_name: "Lucas" >,
+ #<Client id: 2, first_name: "Jan" >,
+ # ...
+]
```
`find_by_sql` provides you with a simple way of making custom calls to the database and retrieving instantiated objects.
@@ -1496,7 +1455,11 @@ Client.find_by_sql("SELECT * FROM clients
`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record.
```ruby
-Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
+Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'")
+# => [
+ {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
+ {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
+]
```
### `pluck`
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 7ec4ab312d..582bb240dd 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -910,8 +910,8 @@ end
The easiest way to add custom validators for validating individual attributes
is with the convenient `ActiveModel::EachValidator`. In this case, the custom
validator class must implement a `validate_each` method which takes three
-arguments: record, attribute and value which correspond to the instance, the
-attribute to be validated and the value of the attribute in the passed
+arguments: record, attribute, and value. These correspond to the instance, the
+attribute to be validated, and the value of the attribute in the passed
instance.
```ruby
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 84cda9222e..e31cefa5bb 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -124,19 +124,22 @@ with a built-in helper. In the source the generated code looked like this:
The query string strategy has several disadvantages:
1. **Not all caches will reliably cache content where the filename only differs by
-query parameters**
+query parameters**
+
[Steve Souders recommends](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/),
"...avoiding a querystring for cacheable resources". He found that in this
case 5-20% of requests will not be cached. Query strings in particular do not
work at all with some CDNs for cache invalidation.
-2. **The file name can change between nodes in multi-server environments.**
+2. **The file name can change between nodes in multi-server environments.**
+
The default query string in Rails 2.x is based on the modification time of
the files. When assets are deployed to a cluster, there is no guarantee that the
timestamps will be the same, resulting in different values being used depending
on which server handles the request.
-3. **Too much cache invalidation**
+3. **Too much cache invalidation**
+
When static assets are deployed with each new release of code, the mtime
(time of last modification) of _all_ these files changes, forcing all remote
clients to fetch them again, even when the content of those assets has not changed.
@@ -490,8 +493,7 @@ The directives that work in JavaScript files also work in stylesheets
one, requiring all stylesheets from the current directory.
In this example, `require_self` is used. This puts the CSS contained within the
-file (if any) at the precise location of the `require_self` call. If
-`require_self` is called more than once, only the last call is respected.
+file (if any) at the precise location of the `require_self` call.
NOTE. If you want to use multiple Sass files, you should generally use the [Sass `@import` rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import)
instead of these Sprockets directives. When using Sprockets directives, Sass files exist within
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 7e99da3f6d..daf4113b66 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -105,7 +105,7 @@ class CreateOrders < ActiveRecord::Migration
end
create_table :orders do |t|
- t.belongs_to :customer
+ t.belongs_to :customer, index: true
t.datetime :order_date
t.timestamps
end
@@ -136,7 +136,7 @@ class CreateSuppliers < ActiveRecord::Migration
end
create_table :accounts do |t|
- t.belongs_to :supplier
+ t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps
end
@@ -169,7 +169,7 @@ class CreateCustomers < ActiveRecord::Migration
end
create_table :orders do |t|
- t.belongs_to :customer
+ t.belongs_to :customer, index:true
t.datetime :order_date
t.timestamps
end
@@ -216,8 +216,8 @@ class CreateAppointments < ActiveRecord::Migration
end
create_table :appointments do |t|
- t.belongs_to :physician
- t.belongs_to :patient
+ t.belongs_to :physician, index: true
+ t.belongs_to :patient, index: true
t.datetime :appointment_date
t.timestamps
end
@@ -295,13 +295,13 @@ class CreateAccountHistories < ActiveRecord::Migration
end
create_table :accounts do |t|
- t.belongs_to :supplier
+ t.belongs_to :supplier, index: true
t.string :account_number
t.timestamps
end
create_table :account_histories do |t|
- t.belongs_to :account
+ t.belongs_to :account, index: true
t.integer :credit_rating
t.timestamps
end
@@ -341,8 +341,8 @@ class CreateAssembliesAndParts < ActiveRecord::Migration
end
create_table :assemblies_parts, id: false do |t|
- t.belongs_to :assembly
- t.belongs_to :part
+ t.belongs_to :assembly, index: true
+ t.belongs_to :part, index: true
end
end
end
@@ -379,6 +379,8 @@ class CreateSuppliers < ActiveRecord::Migration
t.string :account_number
t.timestamps
end
+
+ add_index :accounts, :supplier_id
end
end
```
@@ -455,6 +457,8 @@ class CreatePictures < ActiveRecord::Migration
t.string :imageable_type
t.timestamps
end
+
+ add_index :pictures, :imageable_id
end
end
```
@@ -466,7 +470,7 @@ class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
- t.references :imageable, polymorphic: true
+ t.references :imageable, polymorphic: true, index: true
t.timestamps
end
end
@@ -496,7 +500,7 @@ In your migrations/schema, you will add a references column to the model itself.
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
- t.references :manager
+ t.references :manager, index: true
t.timestamps
end
end
@@ -561,6 +565,8 @@ class CreateOrders < ActiveRecord::Migration
t.string :order_number
t.integer :customer_id
end
+
+ add_index :orders, :customer_id
end
end
```
@@ -594,6 +600,9 @@ class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
t.integer :assembly_id
t.integer :part_id
end
+
+ add_index :assemblies_parts, :assembly_id
+ add_index :assemblies_parts, :part_id
end
end
```
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index cb0228fa75..a074b849c6 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -7,7 +7,6 @@ After reading this guide, you will know:
* How to generate models, controllers, database migrations, and unit tests.
* How to start a development server.
* How to experiment with objects through an interactive shell.
-* How to profile and benchmark your new creation.
--------------------------------------------------------------------------------
@@ -150,8 +149,6 @@ $ bin/rails generate controller Greetings hello
create test/controllers/greetings_controller_test.rb
invoke helper
create app/helpers/greetings_helper.rb
- invoke test_unit
- create test/helpers/greetings_helper_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/greetings.js.coffee
@@ -237,8 +234,6 @@ $ bin/rails generate scaffold HighScore game:string score:integer
create test/controllers/high_scores_controller_test.rb
invoke helper
create app/helpers/high_scores_helper.rb
- invoke test_unit
- create test/helpers/high_scores_helper_test.rb
invoke jbuilder
create app/views/high_scores/index.json.jbuilder
create app/views/high_scores/show.json.jbuilder
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 13020fb286..801cef5ca6 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -137,7 +137,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.assets.enabled` a flag that controls whether the asset
pipeline is enabled. It is set to true by default.
-*`config.assets.raise_runtime_errors`* Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
+* `config.assets.raise_runtime_errors`* Set this flag to `true` to enable additional runtime error checking. Recommended in `config/environments/development.rb` to minimize unexpected behavior when deploying to `production`.
* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/environments/production.rb`.
@@ -151,6 +151,8 @@ pipeline is enabled. It is set to true by default.
* `config.assets.prefix` defines the prefix where assets are served from. Defaults to `/assets`.
+* `config.assets.manifest` defines the full path to be used for the asset precompiler's manifest file. Defaults to a file named `manifest-<random>.json` in the `config.assets.prefix` directory within the public folder.
+
* `config.assets.digest` enables the use of MD5 fingerprints in asset names. Set to `true` by default in `production.rb`.
* `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`.
@@ -996,3 +998,24 @@ If you get the above error, you might want to increase the size of connection
pool by incrementing the `pool` option in `database.yml`
NOTE. If you are running in a multi-threaded environment, there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
+
+
+Custom configuration
+--------------------
+
+You can configure your own code through the Rails configuration object with custom configuration. It works like this:
+
+ ```ruby
+ config.x.payment_processing.schedule = :daily
+ config.x.payment_processing.retries = 3
+ config.x.super_debugger = true
+ ```
+
+These configuration points are then available through the configuration object:
+
+ ```ruby
+ Rails.configuration.x.payment_processing.schedule # => :daily
+ Rails.configuration.x.payment_processing.retries # => 3
+ Rails.configuration.x.super_debugger # => true
+ Rails.configuration.x.super_debugger.not_set # => nil
+ ```
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index a8b959c725..0b05725623 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -318,6 +318,12 @@ You can also run any single test separately:
$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
```
+To run a single test against all adapters, use:
+
+```bash
+$ bundle exec rake TEST=test/cases/associations/has_many_associations_test.rb
+```
+
You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server.
### Warnings
diff --git a/guides/source/engines.md b/guides/source/engines.md
index a5f8ee27b8..24548a5b01 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -136,7 +136,7 @@ following to the dummy application's routes file at
`test/dummy/config/routes.rb`:
```ruby
-mount Blorgh::Engine, at: "blorgh"
+mount Blorgh::Engine => "/blorgh"
```
### Inside an Engine
@@ -173,7 +173,7 @@ Within `lib/blorgh/engine.rb` is the base class for the engine:
```ruby
module Blorgh
- class Engine < Rails::Engine
+ class Engine < ::Rails::Engine
isolate_namespace Blorgh
end
end
@@ -322,8 +322,6 @@ invoke test_unit
create test/controllers/blorgh/articles_controller_test.rb
invoke helper
create app/helpers/blorgh/articles_helper.rb
-invoke test_unit
-create test/helpers/blorgh/articles_helper_test.rb
invoke assets
invoke js
create app/assets/javascripts/blorgh/articles.js
@@ -560,8 +558,6 @@ invoke test_unit
create test/controllers/blorgh/comments_controller_test.rb
invoke helper
create app/helpers/blorgh/comments_helper.rb
-invoke test_unit
-create test/helpers/blorgh/comments_helper_test.rb
invoke assets
invoke js
create app/assets/javascripts/blorgh/comments.js
diff --git a/guides/source/generators.md b/guides/source/generators.md
index 93fb5eece8..5e88fa0c70 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -35,7 +35,7 @@ $ bin/rails generate helper --help
Creating Your First Generator
-----------------------------
-Since Rails 3.0, generators are built on top of [Thor](https://github.com/erikhuda/thor). Thor provides powerful options parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named `initializer.rb` inside `config/initializers`.
+Since Rails 3.0, generators are built on top of [Thor](https://github.com/erikhuda/thor). Thor provides powerful options for parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named `initializer.rb` inside `config/initializers`.
The first step is to create a file at `lib/generators/initializer_generator.rb` with the following content:
@@ -191,8 +191,6 @@ $ bin/rails generate scaffold User name:string
create test/controllers/users_controller_test.rb
invoke helper
create app/helpers/users_helper.rb
- invoke test_unit
- create test/helpers/users_helper_test.rb
invoke jbuilder
create app/views/users/index.json.jbuilder
create app/views/users/show.json.jbuilder
@@ -387,8 +385,6 @@ $ bin/rails generate scaffold Comment body:text
create test/controllers/comments_controller_test.rb
invoke my_helper
create app/helpers/comments_helper.rb
- invoke shoulda
- create test/helpers/comments_helper_test.rb
invoke jbuilder
create app/views/comments/index.json.jbuilder
create app/views/comments/show.json.jbuilder
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index ef97cda3bc..1f91352c82 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -120,7 +120,7 @@ To verify that you have everything installed correctly, you should be able to
run the following:
```bash
-$ bin/rails --version
+$ rails --version
```
If it says something like "Rails 4.2.0", you are ready to continue.
@@ -191,14 +191,15 @@ following in the `blog` directory:
$ bin/rails server
```
-TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the
-absence of a runtime will give you an `execjs` error. Usually Mac OS X and
-Windows come with a JavaScript runtime installed. Rails adds the `therubyracer`
-gem to the generated `Gemfile` in a commented line for new apps and you can
-uncomment if you need it. `therubyrhino` is the recommended runtime for JRuby
-users and is added by default to the `Gemfile` in apps generated under JRuby.
-You can investigate about all the supported runtimes at
-[ExecJS](https://github.com/sstephenson/execjs#readme).
+TIP: Compiling CoffeeScript and JavaScript asset compression requires you
+have a JavaScript runtime available on your system, in the absence
+of a runtime you will see an `execjs` error during asset compilation.
+Usually Mac OS X and Windows come with a JavaScript runtime installed.
+Rails adds the `therubyracer` gem to the generated `Gemfile` in a
+commented line for new apps and you can uncomment if you need it.
+`therubyrhino` is the recommended runtime for JRuby users and is added by
+default to the `Gemfile` in apps generated under JRuby. You can investigate
+about all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme).
This will fire up WEBrick, a web server distributed with Ruby by default. To see
your application in action, open a browser window and navigate to
@@ -256,8 +257,6 @@ invoke test_unit
create test/controllers/welcome_controller_test.rb
invoke helper
create app/helpers/welcome_helper.rb
-invoke test_unit
-create test/helpers/welcome_helper_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/welcome.js.coffee
@@ -909,7 +908,7 @@ And then finally, add the view for this action, located at
</table>
```
-Now if you go to `http://localhost:3000/articles` you will see a list of all the
+Now if you go to <http://localhost:3000/articles> you will see a list of all the
articles that you have created.
### Adding links
@@ -1105,7 +1104,7 @@ standout.
Now you'll get a nice error message when saving an article without title when
you attempt to do just that on the new article form
-[(http://localhost:3000/articles/new)](http://localhost:3000/articles/new).
+<http://localhost:3000/articles/new>:
![Form With Errors](images/getting_started/form_with_errors.png)
@@ -1636,7 +1635,6 @@ This creates six files and one empty directory:
| app/views/comments/ | Views of the controller are stored here |
| test/controllers/comments_controller_test.rb | The test for the controller |
| app/helpers/comments_helper.rb | A view helper file |
-| test/helpers/comments_helper_test.rb | The test for the helper |
| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller |
| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller |
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index a35648d341..f10699fbeb 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -45,7 +45,7 @@ $ bin/rails plugin new yaffle
See usage and options by asking for help:
```bash
-$ bin/rails plugin --help
+$ bin/rails plugin new --help
```
Testing Your Newly Generated Plugin
@@ -440,5 +440,5 @@ $ bin/rake rdoc
* [Developing a RubyGem using Bundler](https://github.com/radar/guides/blob/master/gem-development.md)
* [Using .gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/)
-* [Gemspec Reference](http://docs.rubygems.org/read/chapter/20)
+* [Gemspec Reference](http://guides.rubygems.org/specification-reference/)
* [GemPlugins: A Brief Introduction to the Future of Rails Plugins](http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins)
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 0bd608c007..6512b14e60 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -38,9 +38,11 @@ generate(:scaffold, "person name:string")
route "root to: 'people#index'"
rake("db:migrate")
-git :init
-git add: "."
-git commit: %Q{ -m 'Initial commit' }
+after_bundle do
+ git :init
+ git add: "."
+ git commit: %Q{ -m 'Initial commit' }
+end
```
The following sections outline the primary methods provided by the API:
@@ -228,6 +230,22 @@ git add: "."
git commit: "-a -m 'Initial commit'"
```
+### after_bundle(&block)
+
+Registers a callback to be executed after the gems are bundled and binstubs
+are generated. Useful for all generated files to version control:
+
+```ruby
+after_bundle do
+ git :init
+ git add: '.'
+ git commit: "-a -m 'Initial commit'"
+end
+```
+
+The callbacks gets executed even if `--skip-bundle` and/or `--skip-spring` has
+been passed.
+
Advanced Usage
--------------
diff --git a/guides/source/routing.md b/guides/source/routing.md
index c8f8ba3044..af8c1bbcc4 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -645,6 +645,8 @@ match 'photos', to: 'photos#show', via: :all
NOTE: Routing both `GET` and `POST` requests to a single action has security implications. In general, you should avoid routing all verbs to an action unless you have a good reason to.
+NOTE: 'GET' in Rails won't check for CSRF token. You should never write to the database from 'GET' requests, for more information see the [security guide](security.html#csrf-countermeasures) on CSRF countermeasures.
+
### Segment Constraints
You can use the `:constraints` option to enforce a format for a dynamic segment:
@@ -681,7 +683,7 @@ You can also constrain a route based on any method on the [Request object](actio
You specify a request-based constraint the same way that you specify a segment constraint:
```ruby
-get 'photos', constraints: { subdomain: 'admin' }
+get 'photos', to: 'photos#index', constraints: { subdomain: 'admin' }
```
You can also specify constraints in a block form:
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index f0230b428b..6206b3c715 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -13,17 +13,17 @@ After reading this guide, you will know:
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).
+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), as well as a [cheatsheet](http://daringfireball.net/projects/markdown/basics).
Prologue
--------
-Each guide should start with motivational text at the top (that's the little introduction in the blue area). The prologue should tell the reader what the guide is about, and what they will learn. See for example the [Routing Guide](routing.html).
+Each guide should start with motivational text at the top (that's the little introduction in the blue area). The prologue should tell the reader what the guide is about, and what they will learn. As an example, see the [Routing Guide](routing.html).
-Titles
+Headings
------
-The title of every guide uses `h1`; guide sections use `h2`; subsections `h3`; etc. However, the generated HTML output will have the heading tag starting from `<h2>`.
+The title of every guide uses an `h1` heading; guide sections use `h2` headings; subsections use `h3` headings; etc. Note that the generated HTML output will use heading tags starting with `<h2>`.
```
Guide Title
@@ -35,14 +35,14 @@ Section
### Sub Section
```
-Capitalize all words except for internal articles, prepositions, conjunctions, and forms of the verb to be:
+When writing headings, capitalize all words except for prepositions, conjunctions, internal articles, and forms of the verb "to be":
```
#### Middleware Stack is an Array
#### When are Objects Saved?
```
-Use the same typography as in regular text:
+Use the same inline formatting as regular text:
```
##### The `:content_type` Option
@@ -51,25 +51,23 @@ Use the same typography as in regular text:
API Documentation Guidelines
----------------------------
-The guides and the API should be coherent and consistent where appropriate. Please have a look at these particular sections of the [API Documentation Guidelines](api_documentation_guidelines.html):
+The guides and the API should be coherent and consistent where appropriate. In particular, these sections of the [API Documentation Guidelines](api_documentation_guidelines.html) also apply to the guides:
* [Wording](api_documentation_guidelines.html#wording)
* [Example Code](api_documentation_guidelines.html#example-code)
-* [Filenames](api_documentation_guidelines.html#filenames)
+* [Filenames](api_documentation_guidelines.html#file-names)
* [Fonts](api_documentation_guidelines.html#fonts)
-Those guidelines apply also to guides.
-
HTML Guides
-----------
Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device.
-To install the latest version of Bundler, simply run the `gem install bundler` command
+To install the latest version of Bundler, run `gem install bundler`.
### Generation
-To generate all the guides, just `cd` into the `guides` directory, run `bundle install` and execute:
+To generate all the guides, just `cd` into the `guides` directory, run `bundle install`, and execute:
```
bundle exec rake guides:generate
diff --git a/guides/source/testing.md b/guides/source/testing.md
index a88625a40a..2ecd560a87 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -365,7 +365,7 @@ Ideally, you would like to include a test for everything which could possibly br
By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned.
There are a bunch of different types of assertions you can use.
-Here's an extract of the assertions you can use with `minitest`, the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required.
+Here's an extract of the assertions you can use with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required.
| Assertion | Purpose |
| ---------------------------------------------------------------- | ------- |
@@ -377,8 +377,12 @@ Here's an extract of the assertions you can use with `minitest`, the default tes
| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.|
| `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.|
| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.|
+| `assert_empty( obj, [msg] )` | Ensures that `obj` is `empty?`.|
+| `assert_not_empty( obj, [msg] )` | Ensures that `obj` is not `empty?`.|
| `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.|
| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.|
+| `assert_includes( collection, obj, [msg] )` | Ensures that `obj` is in `collection`.|
+| `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.|
| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
@@ -392,9 +396,13 @@ Here's an extract of the assertions you can use with `minitest`, the default tes
| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.|
| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.|
| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.|
+| `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`|
+| `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`|
| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?|
| `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.|
+The above are subset of assertions that minitest supports. For an exhaustive & more up-to-date list, please check [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically [`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)
+
Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier.
NOTE: Creating your own assertions is an advanced topic that we won't cover in this tutorial.
@@ -1006,17 +1014,9 @@ Testing helpers
In order to test helpers, all you need to do is check that the output of the
helper method matches what you'd expect. Tests related to the helpers are
-located under the `test/helpers` directory. Rails provides a generator which
-generates both the helper and the test file:
-
-```bash
-$ bin/rails generate helper User
- create app/helpers/user_helper.rb
- invoke test_unit
- create test/helpers/user_helper_test.rb
-```
+located under the `test/helpers` directory.
-The generated test file contains the following code:
+A helper test looks like so:
```ruby
require 'test_helper'
@@ -1049,7 +1049,6 @@ The built-in `minitest` based testing is not the only way to test Rails applicat
* [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.
-* [Machinist](https://github.com/notahat/machinist/tree/master), another replacement for fixtures.
* [Fixture Builder](https://github.com/rdy/fixture_builder), a tool that compiles Ruby factories into fixtures before a test run.
* [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests.
* [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions.
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 36cd505977..cc20782780 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -52,6 +52,44 @@ Upgrading from Rails 4.1 to Rails 4.2
NOTE: This section is a work in progress.
+### Serialized attributes
+
+When using a custom coder (e.g. `serialize :metadata, JSON`),
+assigning `nil` to a serialized attribute will save it to the database
+as `NULL` instead of passing the `nil` value through the coder (e.g. `"null"`
+when using the `JSON` coder).
+
+### `after_bundle` in Rails templates
+
+If you have a Rails template that adds all the files in version control, it
+fails to add the generated binstubs because it gets executed before Bundler:
+
+```ruby
+# template.rb
+generate(:scaffold, "person name:string")
+route "root to: 'people#index'"
+rake("db:migrate")
+
+git :init
+git add: "."
+git commit: %Q{ -m 'Initial commit' }
+```
+
+You can now wrap the `git` calls in an `after_bundle` block. It will be run
+after the binstubs have been generated.
+
+```ruby
+# template.rb
+generate(:scaffold, "person name:string")
+route "root to: 'people#index'"
+rake("db:migrate")
+
+after_bundle do
+ git :init
+ git add: "."
+ git commit: %Q{ -m 'Initial commit' }
+end
+```
Upgrading from Rails 4.0 to Rails 4.1
-------------------------------------
@@ -587,6 +625,9 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep
* Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. You shouldn't use instance methods since it's now deprecated. You should change them to use class methods, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`.
+* When using the default coder, assigning `nil` to a serialized attribute will save it
+to the database as `NULL` instead of passing the `nil` value through YAML (`"--- \n...\n"`).
+
* Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) for a smooth upgrade path.
* If you are not using Protected Attributes, you can remove any options related to
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index c33a4ed192..1ccdfb6589 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,4 +1,51 @@
-* Deprecate `Rails::Rack::LogTailer` with not replacement.
+* Add `after_bundle` callbacks in Rails templates. Useful for allowing the
+ generated binstubs to be added to version control.
+
+ Fixes #16292.
+
+ *Stefan Kanev*
+
+* Pull in the custom configuration concept from dhh/custom_configuration, which allows you to
+ configure your own code through the Rails configuration object with custom configuration:
+
+ # config/environments/production.rb
+ config.x.payment_processing.schedule = :daily
+ config.x.payment_processing.retries = 3
+ config.x.super_debugger = true
+
+ These configuration points are then available through the configuration object:
+
+ Rails.configuration.x.payment_processing.schedule # => :daily
+ Rails.configuration.x.payment_processing.retries # => 3
+ Rails.configuration.x.super_debugger # => true
+ Rails.configuration.x.super_debugger.not_set # => nil
+
+ *DHH*
+
+* Scaffold generator `_form` partial adds `class="field"` for password
+ confirmation fields.
+
+ *noinkling*
+
+* Add `Rails::Application.config_for` to load a configuration for the current
+ environment.
+
+ # config/exception_notification.yml:
+ production:
+ url: http://127.0.0.1:8080
+ namespace: my_app_production
+ development:
+ url: http://localhost:3001
+ namespace: my_app_development
+
+ # config/production.rb
+ MyApp::Application.configure do
+ config.middleware.use ExceptionNotifier, config_for(:exception_notification)
+ end
+
+ *Rafael Mendonça França*, *DHH*
+
+* Deprecate `Rails::Rack::LogTailer` without replacement.
*Rafael Mendonça França*
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index ecd8c22dd8..e7172e491f 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -29,7 +29,13 @@ module Rails
autoload :WelcomeController
class << self
- attr_accessor :application, :cache, :logger
+ @application = @app_class = nil
+
+ attr_writer :application
+ attr_accessor :app_class, :cache, :logger
+ def application
+ @application ||= (app_class.instance if app_class)
+ end
delegate :initialize!, :initialized?, to: :application
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb
index 56f05b3844..39d8007333 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_rails_loader.rb
@@ -2,6 +2,8 @@ require 'pathname'
module Rails
module AppRailsLoader
+ extend self
+
RUBY = Gem.ruby
EXECUTABLES = ['bin/rails', 'script/rails']
BUNDLER_WARNING = <<EOS
@@ -26,7 +28,7 @@ generate it and add it to source control:
EOS
- def self.exec_app_rails
+ def exec_app_rails
original_cwd = Dir.pwd
loop do
@@ -54,7 +56,7 @@ EOS
end
end
- def self.find_executable
+ def find_executable
EXECUTABLES.find { |exe| File.file?(exe) }
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 362713eb75..61639be7c6 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -87,7 +87,15 @@ module Rails
class << self
def inherited(base)
super
- base.instance
+ Rails.app_class = base
+ end
+
+ def instance
+ super.run_load_hooks!
+ end
+
+ def create(initial_variable_values = {}, &block)
+ new(initial_variable_values, &block).run_load_hooks!
end
# Makes the +new+ method public.
@@ -116,24 +124,33 @@ module Rails
@ordered_railties = nil
@railties = nil
@message_verifiers = {}
+ @ran_load_hooks = false
- Rails.application ||= self
+ # are these actually used?
+ @initial_variable_values = initial_variable_values
+ @block = block
add_lib_to_load_path!
+ end
+
+ # Returns true if the application is initialized.
+ def initialized?
+ @initialized
+ end
+
+ def run_load_hooks! # :nodoc:
+ return self if @ran_load_hooks
+ @ran_load_hooks = true
ActiveSupport.run_load_hooks(:before_configuration, self)
- initial_variable_values.each do |variable_name, value|
+ @initial_variable_values.each do |variable_name, value|
if INITIAL_VARIABLES.include?(variable_name)
instance_variable_set("@#{variable_name}", value)
end
end
- instance_eval(&block) if block_given?
- end
-
- # Returns true if the application is initialized.
- def initialized?
- @initialized
+ instance_eval(&@block) if @block
+ self
end
# Implements call according to the Rack API. It simply
@@ -187,6 +204,38 @@ module Rails
end
end
+ # Convenience for loading config/foo.yml for the current Rails env.
+ #
+ # Example:
+ #
+ # # config/exception_notification.yml:
+ # production:
+ # url: http://127.0.0.1:8080
+ # namespace: my_app_production
+ # development:
+ # url: http://localhost:3001
+ # namespace: my_app_development
+ #
+ # # config/production.rb
+ # MyApp::Application.configure do
+ # config.middleware.use ExceptionNotifier, config_for(:exception_notification)
+ # end
+ def config_for(name)
+ yaml = Pathname.new("#{paths["config"].existent.first}/#{name}.yml")
+
+ if yaml.exist?
+ require "yaml"
+ require "erb"
+ (YAML.load(ERB.new(yaml.read).result) || {})[Rails.env] || {}
+ else
+ raise "Could not load configuration. No such file - #{yaml}"
+ end
+ rescue Psych::SyntaxError => e
+ raise "YAML syntax error occurred while parsing #{yaml}. " \
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
+ "Error: #{e.message}"
+ end
+
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
def env_config
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 5e8f4de847..782bc4b0f1 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -13,7 +13,7 @@ module Rails
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
- :beginning_of_week, :filter_redirect
+ :beginning_of_week, :filter_redirect, :x
attr_writer :log_level
attr_reader :encoding
@@ -48,6 +48,7 @@ module Rails
@eager_load = nil
@secret_token = nil
@secret_key_base = nil
+ @x = Custom.new
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = true
@@ -154,6 +155,17 @@ module Rails
def annotations
SourceAnnotationExtractor::Annotation
end
+
+ private
+ class Custom
+ def initialize
+ @configurations = Hash.new
+ end
+
+ def method_missing(method, *args)
+ @configurations[method] ||= ActiveSupport::OrderedOptions.new
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 6146b6c1db..c3b7bb6f84 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -9,7 +9,17 @@ module Rails
def parse!(args)
args, options = args.dup, {}
- opt_parser = OptionParser.new do |opts|
+ option_parser(options).parse! args
+
+ options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development"
+ options[:server] = args.shift
+ options
+ end
+
+ private
+
+ def option_parser(options)
+ OptionParser.new do |opts|
opts.banner = "Usage: rails server [mongrel, thin, etc] [options]"
opts.on("-p", "--port=port", Integer,
"Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
@@ -37,12 +47,6 @@ module Rails
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
end
-
- opt_parser.parse! args
-
- options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development"
- options[:server] = args.shift
- options
end
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index b36ab3d0d5..dc3da1eb41 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -395,7 +395,7 @@ module Rails
end
unless mod.respond_to?(:railtie_routes_url_helpers)
- define_method(:railtie_routes_url_helpers) { railtie.routes.url_helpers }
+ define_method(:railtie_routes_url_helpers) {|include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) }
end
end
end
@@ -509,7 +509,7 @@ module Rails
def call(env)
env.merge!(env_config)
if env['SCRIPT_NAME']
- env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
+ env["ROUTES_#{routes.object_id}_SCRIPT_NAME"] = env['SCRIPT_NAME'].dup
end
app.call(env)
end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index a239874df0..4709914947 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -7,6 +7,7 @@ module Rails
def initialize(*) # :nodoc:
super
@in_group = nil
+ @after_bundle_callbacks = []
end
# Adds an entry into +Gemfile+ for the supplied gem.
@@ -232,6 +233,16 @@ module Rails
log File.read(find_in_source_paths(path))
end
+ # Registers a callback to be executed after bundle and spring binstubs
+ # have run.
+ #
+ # after_bundle do
+ # git add: '.'
+ # end
+ def after_bundle(&block)
+ @after_bundle_callbacks << block
+ end
+
protected
# Define log for backwards compatibility. If just one argument is sent,
diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb
index cf3b7acfff..682092fdf2 100644
--- a/railties/lib/rails/generators/actions/create_migration.rb
+++ b/railties/lib/rails/generators/actions/create_migration.rb
@@ -55,7 +55,8 @@ module Rails
else
say_status :conflict, :red
raise Error, "Another migration is already named #{migration_file_name}: " +
- "#{existing_migration}. Use --force to replace this migration file."
+ "#{existing_migration}. Use --force to replace this migration " +
+ "or --skip to ignore conflicted file."
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 20e512a7ff..caaaae09e6 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -113,7 +113,7 @@ module Rails
javascript_gemfile_entry,
jbuilder_gemfile_entry,
sdoc_gemfile_entry,
- spring_gemfile_entry,
+ psych_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -194,21 +194,19 @@ module Rails
def self.path(name, path, comment = nil)
new(name, nil, comment, path: path)
end
-
- def padding(max_width)
- ' ' * (max_width - name.length + 2)
- end
end
def rails_gemfile_entry
if options.dev?
[GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
GemfileEntry.github('arel', 'rails/arel'),
- GemfileEntry.github('rack', 'rack/rack')]
+ GemfileEntry.github('rack', 'rack/rack'),
+ GemfileEntry.github('i18n', 'svenfuchs/i18n')]
elsif options.edge?
[GemfileEntry.github('rails', 'rails/rails'),
GemfileEntry.github('arel', 'rails/arel'),
- GemfileEntry.github('rack', 'rack/rack')]
+ GemfileEntry.github('rack', 'rack/rack'),
+ GemfileEntry.github('i18n', 'svenfuchs/i18n')]
else
[GemfileEntry.version('rails',
Rails::VERSION::STRING,
@@ -307,10 +305,12 @@ module Rails
end
end
- def spring_gemfile_entry
- return [] unless spring_install?
- comment = 'Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring'
- GemfileEntry.new('spring', nil, comment, group: :development)
+ def psych_gemfile_entry
+ return [] unless defined?(Rubinius)
+
+ comment = 'Use Psych as the YAML engine, instead of Syck, so serialized ' \
+ 'data can be read safely from different rubies (see http://git.io/uuLVag)'
+ GemfileEntry.new('psych', '~> 2.0', comment, platforms: :rbx)
end
def bundle_command(command)
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 da99e74435..bba9141fb8 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -17,7 +17,7 @@
<%%= f.label :password %><br>
<%%= f.password_field :password %>
</div>
- <div>
+ <div class="field">
<%%= f.label :password_confirmation %><br>
<%%= f.password_field :password_confirmation %>
<% else -%>
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 188e62b6c8..9110c129d1 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -259,6 +259,12 @@ module Rails
public_task :apply_rails_template, :run_bundle
public_task :generate_spring_binstubs
+ def run_after_bundle_callbacks
+ @after_bundle_callbacks.each do |callback|
+ callback.call
+ end
+ end
+
protected
def self.banner
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 5bdbd58097..8b51fda359 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -1,6 +1,5 @@
source 'https://rubygems.org'
-<% max_width = gemfile_entries.map { |g| g.name.length }.max -%>
<% gemfile_entries.each do |gem| -%>
<% if gem.comment -%>
@@ -8,7 +7,7 @@ source 'https://rubygems.org'
<% end -%>
<%= gem.commented_out ? '# ' : '' %>gem '<%= gem.name %>'<%= %(, '#{gem.version}') if gem.version -%>
<% if gem.options.any? -%>
-,<%= gem.padding(max_width) %><%= gem.options.map { |k,v|
+, <%= gem.options.map { |k,v|
"#{k}: #{v.inspect}" }.join(', ') %>
<% end -%>
<% end -%>
@@ -22,14 +21,23 @@ source 'https://rubygems.org'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
+group :development, :test do
<% unless defined?(JRUBY_VERSION) -%>
-# To use a debugger
+ # Call 'debugger' anywhere in the code to stop execution and get a debugger console
<%- if RUBY_VERSION < '2.0.0' -%>
-# gem 'debugger', group: [:development, :test]
+ gem 'debugger'
<%- else -%>
-# gem 'byebug', group: [:development, :test]
+ gem 'byebug'
<%- end -%>
+
+ # Access an IRB console on exceptions page and /console in development
+ gem 'web-console'
+<%- if spring_install? %>
+ # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
+ gem 'spring'
+<% end -%>
<% end -%>
+end
<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince/) -%>
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
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 bbb409616d..35e3035a0b 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,7 +30,8 @@ Rails.application.configure do
# number of complex assets.
config.assets.debug = true
- # Generate digests for assets URLs.
+ # Asset digests allow you to set far-future HTTP expiration dates on all assets,
+ # yet still be able to expire them through the digest params.
config.assets.digest = true
# Adds additional error checking when serving assets at runtime.
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 5e52f97249..277fe01e89 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
@@ -30,7 +30,8 @@ Rails.application.configure do
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # Generate digests for assets URLs.
+ # Asset digests allow you to set far-future HTTP expiration dates on all assets,
+ # yet still be able to expire them through the digest params.
config.assets.digest = true
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
@@ -43,8 +44,8 @@ Rails.application.configure do
# 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
+ # Set to :info to decrease the log volume.
+ config.log_level = :debug
# Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
index d2f4ec33a6..01ef3e6630 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -3,6 +3,9 @@
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
+# Add additional assets to the asset load path
+# Rails.application.config.assets.paths << Emoji.images_path
+
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
diff --git a/railties/lib/rails/generators/rails/controller/USAGE b/railties/lib/rails/generators/rails/controller/USAGE
index de33900e0a..64239ad599 100644
--- a/railties/lib/rails/generators/rails/controller/USAGE
+++ b/railties/lib/rails/generators/rails/controller/USAGE
@@ -16,4 +16,3 @@ Example:
Test: test/controllers/credit_cards_controller_test.rb
Views: app/views/credit_cards/debit.html.erb [...]
Helper: app/helpers/credit_cards_helper.rb
- Test: test/helpers/credit_cards_helper_test.rb
diff --git a/railties/lib/rails/generators/rails/helper/USAGE b/railties/lib/rails/generators/rails/helper/USAGE
index 30e323a858..8855ef3b01 100644
--- a/railties/lib/rails/generators/rails/helper/USAGE
+++ b/railties/lib/rails/generators/rails/helper/USAGE
@@ -5,13 +5,9 @@ Description:
To create a helper within a module, specify the helper name as a
path like 'parent_module/helper_name'.
- This generates a helper class in app/helpers and invokes the configured
- test framework.
-
Example:
`rails generate helper CreditCard`
Credit card helper.
Helper: app/helpers/credit_card_helper.rb
- Test: test/helpers/credit_card_helper_test.rb
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 833b7beb7f..2a6b8700e3 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -6,6 +6,11 @@ Description:
model's attributes. Timestamps are added by default, so you don't have to
specify them by hand as 'created_at:datetime updated_at:datetime'.
+ As a special case, specifying 'password:digest' will generate a
+ password_digest field of string type, and configure your generated model and
+ tests for use with ActiveModel has_secure_password (assuming the default ORM
+ and test framework are being used).
+
You don't have to think up every attribute up front, but it helps to
sketch out a few so you can start working with the model immediately.
@@ -27,7 +32,8 @@ Available field types:
`rails generate model post title:string body:text`
will generate a title column with a varchar type and a body column with a text
- type. You can use the following types:
+ type. If no type is specified the string type will be used by default.
+ You can use the following types:
integer
primary_key
@@ -73,6 +79,10 @@ Available field types:
`rails generate model user username:string{30}:uniq`
`rails generate model product supplier:references{polymorphic}:index`
+ If you require a `password_digest` string column for use with
+ has_secure_password, you should specify `password:digest`:
+
+ `rails generate model user password:digest`
Examples:
`rails generate model account`
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index 796587f316..35ad9fbf9e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -31,7 +31,7 @@ end
<% end -%>
<%= gem.commented_out ? '# ' : '' %>gem '<%= gem.name %>'<%= %(, '#{gem.version}') if gem.version -%>
<% if gem.options.any? -%>
-,<%= gem.padding(max_width) %><%= gem.options.map { |k,v|
+, <%= gem.options.map { |k,v|
"#{k}: #{v.inspect}" }.join(', ') %>
<% end -%>
<% end -%>
diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE
index 4a3eb2c7c7..1b2a944103 100644
--- a/railties/lib/rails/generators/rails/scaffold/USAGE
+++ b/railties/lib/rails/generators/rails/scaffold/USAGE
@@ -9,11 +9,16 @@ Description:
Attributes are field arguments specifying the model's attributes. You can
optionally pass the type and an index to each field. For instance:
- "title body:text tracking_id:integer:uniq" will generate a title field of
+ 'title body:text tracking_id:integer:uniq' will generate a title field of
string type, a body with text type and a tracking_id as an integer with an
unique index. "index" could also be given instead of "uniq" if one desires
a non unique index.
+ As a special case, specifying 'password:digest' will generate a
+ password_digest field of string type, and configure your generated model,
+ controller, views, and test suite for use with ActiveModel
+ has_secure_password (assuming they are using Rails defaults).
+
Timestamps are added by default, so you don't have to specify them by hand
as 'created_at:datetime updated_at:datetime'.
@@ -33,3 +38,4 @@ Examples:
`rails generate scaffold post`
`rails generate scaffold post title body:text published:boolean`
`rails generate scaffold purchase amount:decimal tracking_id:integer:uniq`
+ `rails generate scaffold user email:uniq password:digest`
diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
index 0db76f9eaf..bde4e88915 100644
--- a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
+++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
@@ -3,11 +3,7 @@ require 'rails/generators/test_unit'
module TestUnit # :nodoc:
module Generators # :nodoc:
class HelperGenerator < Base # :nodoc:
- check_class_collision suffix: "HelperTest"
-
- def create_helper_files
- template 'helper_test.rb', File.join('test/helpers', class_path, "#{file_name}_helper_test.rb")
- end
+ # Rails does not generate anything here.
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb b/railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb
deleted file mode 100644
index 7d37bda0f9..0000000000
--- a/railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-require 'test_helper'
-
-<% module_namespacing do -%>
-class <%= class_name %>HelperTest < ActionView::TestCase
-end
-<% end -%>
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index 7576eba6e0..e0600d0b59 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -100,6 +100,23 @@ module Rails
dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, '')
Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first
end
+
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ end
end
end
end
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 3b35798679..9962e6d943 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -34,7 +34,7 @@ module Rails
instrumenter = ActiveSupport::Notifications.instrumenter
instrumenter.start 'request.action_dispatch', request: request
- logger.info started_request_message(request)
+ logger.info { started_request_message(request) }
resp = @app.call(env)
resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
resp
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 9ccc286b4e..d8800eaa0f 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -26,3 +26,30 @@ end
def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
+
+class ActiveSupport::TestCase
+ # FIXME: we have tests that depend on run order, we should fix that and
+ # remove this method call.
+ self.my_tests_are_order_dependent!
+
+ private
+
+ unless defined?(:capture)
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ end
+ end
+end
diff --git a/railties/test/app_rails_loader_test.rb b/railties/test/app_rails_loader_test.rb
index 1d3b80253a..d4885447e6 100644
--- a/railties/test/app_rails_loader_test.rb
+++ b/railties/test/app_rails_loader_test.rb
@@ -3,13 +3,27 @@ require 'abstract_unit'
require 'rails/app_rails_loader'
class AppRailsLoaderTest < ActiveSupport::TestCase
+ def loader
+ @loader ||= Class.new do
+ extend Rails::AppRailsLoader
+
+ def self.exec_arguments
+ @exec_arguments
+ end
+
+ def self.exec(*args)
+ @exec_arguments = args
+ end
+ end
+ end
+
def write(filename, contents=nil)
FileUtils.mkdir_p(File.dirname(filename))
File.write(filename, contents)
end
def expects_exec(exe)
- Rails::AppRailsLoader.expects(:exec).with(Rails::AppRailsLoader::RUBY, exe)
+ assert_equal [Rails::AppRailsLoader::RUBY, exe], loader.exec_arguments
end
setup do
@@ -22,30 +36,30 @@ class AppRailsLoaderTest < ActiveSupport::TestCase
exe = "#{script_dir}/rails"
test "is not in a Rails application if #{exe} is not found in the current or parent directories" do
- File.stubs(:file?).with('bin/rails').returns(false)
- File.stubs(:file?).with('script/rails').returns(false)
+ def loader.find_executables; end
- assert !Rails::AppRailsLoader.exec_app_rails
+ assert !loader.exec_app_rails
end
test "is not in a Rails application if #{exe} exists but is a folder" do
FileUtils.mkdir_p(exe)
- assert !Rails::AppRailsLoader.exec_app_rails
+ assert !loader.exec_app_rails
end
['APP_PATH', 'ENGINE_PATH'].each do |keyword|
test "is in a Rails application if #{exe} exists and contains #{keyword}" do
write exe, keyword
+ loader.exec_app_rails
+
expects_exec exe
- Rails::AppRailsLoader.exec_app_rails
end
test "is not in a Rails application if #{exe} exists but doesn't contain #{keyword}" do
write exe
- assert !Rails::AppRailsLoader.exec_app_rails
+ assert !loader.exec_app_rails
end
test "is in a Rails application if parent directory has #{exe} containing #{keyword} and chdirs to the root directory" do
@@ -54,8 +68,9 @@ class AppRailsLoaderTest < ActiveSupport::TestCase
Dir.chdir('foo/bar')
+ loader.exec_app_rails
+
expects_exec exe
- Rails::AppRailsLoader.exec_app_rails
# Compare the realpath in case either of them has symlinks.
#
diff --git a/railties/test/application/configuration/base_test.rb b/railties/test/application/configuration/base_test.rb
new file mode 100644
index 0000000000..d6a82b139d
--- /dev/null
+++ b/railties/test/application/configuration/base_test.rb
@@ -0,0 +1,37 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+require 'env_helpers'
+
+module ApplicationTests
+ module ConfigurationTests
+ class BaseTest < ActiveSupport::TestCase
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ end
+
+ def teardown
+ teardown_app
+ FileUtils.rm_rf(new_app) if File.directory?(new_app)
+ end
+
+ private
+ def new_app
+ File.expand_path("#{app_path}/../new_app")
+ end
+
+ def copy_app
+ FileUtils.cp_r(app_path, new_app)
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ def require_environment
+ require "#{app_path}/config/environment"
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb
new file mode 100644
index 0000000000..045537fc28
--- /dev/null
+++ b/railties/test/application/configuration/custom_test.rb
@@ -0,0 +1,15 @@
+require 'application/configuration/base_test'
+
+class ApplicationTests::ConfigurationTests::CustomTest < ApplicationTests::ConfigurationTests::BaseTest
+ test 'access custom configuration point' do
+ add_to_config <<-RUBY
+ config.x.resque.inline_jobs = :always
+ config.x.resque.timeout = 60
+ RUBY
+ require_environment
+
+ assert_equal :always, Rails.configuration.x.resque.inline_jobs
+ assert_equal 60, Rails.configuration.x.resque.timeout
+ assert_nil Rails.configuration.x.resque.nothing
+ end
+end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 2e056be561..e661b6f4cc 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -66,13 +66,25 @@ module ApplicationTests
config.action_dispatch.show_exceptions = true
RUBY
+ app_file 'db/migrate/20140708012246_create_user.rb', <<-RUBY
+ class CreateUser < ActiveRecord::Migration
+ def change
+ create_table :users
+ end
+ end
+ RUBY
+
require "#{app_path}/config/environment"
- ActiveRecord::Migrator.stubs(:needs_migration?).returns(true)
- ActiveRecord::NullMigration.any_instance.stubs(:mtime).returns(1)
- get "/foo"
- assert_equal 500, last_response.status
- assert_match "ActiveRecord::PendingMigrationError", last_response.body
+ ActiveRecord::Migrator.migrations_paths = ["#{app_path}/db/migrate"]
+
+ begin
+ get "/foo"
+ assert_equal 500, last_response.status
+ assert_match "ActiveRecord::PendingMigrationError", last_response.body
+ ensure
+ ActiveRecord::Migrator.migrations_paths = nil
+ end
end
test "Rails.groups returns available groups" do
@@ -372,6 +384,8 @@ module ApplicationTests
end
RUBY
+ token = "cf50faa3fe97702ca1ae"
+
app_file 'app/controllers/posts_controller.rb', <<-RUBY
class PostsController < ApplicationController
def show
@@ -381,6 +395,10 @@ module ApplicationTests
def update
render text: "update"
end
+
+ private
+
+ def form_authenticity_token; token; end # stub the authenticy token
end
RUBY
@@ -392,8 +410,6 @@ module ApplicationTests
require "#{app_path}/config/environment"
- token = "cf50faa3fe97702ca1ae"
- PostsController.any_instance.stubs(:form_authenticity_token).returns(token)
params = {authenticity_token: token}
get "/posts/1"
@@ -424,7 +440,7 @@ module ApplicationTests
end
get "/"
- assert last_response.body =~ /_xsrf_token_here/
+ assert_match "_xsrf_token_here", last_response.body
end
test "sets ActionDispatch.test_app" do
@@ -875,79 +891,79 @@ module ApplicationTests
end
test "rake_tasks block works at instance level" do
- $ran_block = false
-
app_file "config/environments/development.rb", <<-RUBY
Rails.application.configure do
+ config.ran_block = false
+
rake_tasks do
- $ran_block = true
+ config.ran_block = true
end
end
RUBY
require "#{app_path}/config/environment"
+ assert_not Rails.configuration.ran_block
- assert !$ran_block
require 'rake'
require 'rake/testtask'
require 'rdoc/task'
Rails.application.load_tasks
- assert $ran_block
+ assert Rails.configuration.ran_block
end
test "generators block works at instance level" do
- $ran_block = false
-
app_file "config/environments/development.rb", <<-RUBY
Rails.application.configure do
+ config.ran_block = false
+
generators do
- $ran_block = true
+ config.ran_block = true
end
end
RUBY
require "#{app_path}/config/environment"
+ assert_not Rails.configuration.ran_block
- assert !$ran_block
Rails.application.load_generators
- assert $ran_block
+ assert Rails.configuration.ran_block
end
test "console block works at instance level" do
- $ran_block = false
-
app_file "config/environments/development.rb", <<-RUBY
Rails.application.configure do
+ config.ran_block = false
+
console do
- $ran_block = true
+ config.ran_block = true
end
end
RUBY
require "#{app_path}/config/environment"
+ assert_not Rails.configuration.ran_block
- assert !$ran_block
Rails.application.load_console
- assert $ran_block
+ assert Rails.configuration.ran_block
end
test "runner block works at instance level" do
- $ran_block = false
-
app_file "config/environments/development.rb", <<-RUBY
Rails.application.configure do
+ config.ran_block = false
+
runner do
- $ran_block = true
+ config.ran_block = true
end
end
RUBY
require "#{app_path}/config/environment"
+ assert_not Rails.configuration.ran_block
- assert !$ran_block
Rails.application.load_runner
- assert $ran_block
+ assert Rails.configuration.ran_block
end
test "loading the first existing database configuration available" do
@@ -961,9 +977,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
- db_config = Rails.application.config.database_configuration
-
- assert db_config.is_a?(Hash)
+ assert_kind_of Hash, Rails.application.config.database_configuration
end
test 'config.action_mailer.show_previews defaults to true in development' do
@@ -977,7 +991,7 @@ module ApplicationTests
Rails.env = "production"
require "#{app_path}/config/environment"
- assert_equal Rails.application.config.action_mailer.show_previews, false
+ assert_equal false, Rails.application.config.action_mailer.show_previews
end
test 'config.action_mailer.show_previews can be set in the configuration file' do
@@ -987,7 +1001,91 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- assert_equal Rails.application.config.action_mailer.show_previews, true
+ assert_equal true, Rails.application.config.action_mailer.show_previews
+ end
+
+ test "config_for loads custom configuration from yaml files" do
+ app_file 'config/custom.yml', <<-RUBY
+ development:
+ key: 'custom key'
+ RUBY
+
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for('custom')
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal 'custom key', Rails.application.config.my_custom_config['key']
+ end
+
+ test "config_for raises an exception if the file does not exist" do
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for('custom')
+ RUBY
+
+ exception = assert_raises(RuntimeError) do
+ require "#{app_path}/config/environment"
+ end
+
+ assert_equal "Could not load configuration. No such file - #{app_path}/config/custom.yml", exception.message
+ end
+
+ test "config_for without the environment configured returns an empty hash" do
+ app_file 'config/custom.yml', <<-RUBY
+ test:
+ key: 'custom key'
+ RUBY
+
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for('custom')
+ RUBY
+ require "#{app_path}/config/environment"
+
+ assert_equal({}, Rails.application.config.my_custom_config)
+ end
+
+ test "config_for with empty file returns an empty hash" do
+ app_file 'config/custom.yml', <<-RUBY
+ RUBY
+
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for('custom')
+ RUBY
+ require "#{app_path}/config/environment"
+
+ assert_equal({}, Rails.application.config.my_custom_config)
+ end
+
+ test "config_for containing ERB tags should evaluate" do
+ app_file 'config/custom.yml', <<-RUBY
+ development:
+ key: <%= 'custom key' %>
+ RUBY
+
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for('custom')
+ RUBY
+ require "#{app_path}/config/environment"
+
+ assert_equal 'custom key', Rails.application.config.my_custom_config['key']
+ end
+
+ test "config_for with syntax error show a more descritive exception" do
+ app_file 'config/custom.yml', <<-RUBY
+ development:
+ key: foo:
+ RUBY
+
+ add_to_config <<-RUBY
+ config.my_custom_config = config_for('custom')
+ RUBY
+
+ exception = assert_raises(RuntimeError) do
+ require "#{app_path}/config/environment"
+ end
+
+ assert_match 'YAML syntax error occurred while parsing', exception.message
end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 8e76bf27f3..ae550331bd 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -50,7 +50,7 @@ module ApplicationTests
assert_equal "test.rails", ActionMailer::Base.default_url_options[:host]
end
- test "does not include url helpers as action methods" do
+ test "includes url helpers as action methods" do
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get "/foo", :to => lambda { |env| [200, {}, []] }, :as => :foo
@@ -66,8 +66,8 @@ module ApplicationTests
require "#{app_path}/config/environment"
assert Foo.method_defined?(:foo_path)
+ assert Foo.method_defined?(:foo_url)
assert Foo.method_defined?(:main_app)
- assert_equal Set.new(["notify"]), Foo.action_methods
end
test "allows to not load all helpers for controllers" do
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index bc34897cdf..9ee54796a4 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -184,28 +184,13 @@ en:
assert_fallbacks ca: [:ca, :"es-ES", :es, :'en-US', :en]
end
- test "config.i18n.enforce_available_locales is set to true by default and avoids I18n warnings" do
- add_to_config <<-RUBY
- config.i18n.default_locale = :it
- RUBY
-
- output = capture(:stderr) { load_app }
- assert_no_match %r{deprecated.*enforce_available_locales}, output
- assert_equal true, I18n.enforce_available_locales
-
- assert_raise I18n::InvalidLocale do
- I18n.locale = :es
- end
- end
-
test "disable config.i18n.enforce_available_locales" do
add_to_config <<-RUBY
config.i18n.enforce_available_locales = false
config.i18n.default_locale = :fr
RUBY
- output = capture(:stderr) { load_app }
- assert_no_match %r{deprecated.*enforce_available_locales}, output
+ load_app
assert_equal false, I18n.enforce_available_locales
assert_nothing_raised do
@@ -220,8 +205,7 @@ en:
config.i18n.default_locale = :fr
RUBY
- output = capture(:stderr) { load_app }
- assert_no_match %r{deprecated.*enforce_available_locales}, output
+ load_app
assert_equal false, I18n.enforce_available_locales
assert_nothing_raised do
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 8b91a1171f..55e917c3ec 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -417,6 +417,58 @@ module ApplicationTests
assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
end
+ test "*_path helpers emit a deprecation" do
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get 'foo', to: 'foo#index'
+ end
+ RUBY
+
+ mailer 'notifier', <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def path_in_view
+ mail to: "to@example.org"
+ end
+
+ def path_in_mailer
+ @url = foo_path
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ html_template 'notifier/path_in_view', "<%= link_to 'foo', foo_path %>"
+
+ mailer_preview 'notifier', <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def path_in_view
+ Notifier.path_in_view
+ end
+
+ def path_in_mailer
+ Notifier.path_in_mailer
+ end
+ end
+ RUBY
+
+ app('development')
+
+ assert_deprecated do
+ get "/rails/mailers/notifier/path_in_view.html"
+ assert_equal 200, last_response.status
+ end
+
+ html_template 'notifier/path_in_mailer', "No ERB in here"
+
+ assert_deprecated do
+ get "/rails/mailers/notifier/path_in_mailer.html"
+ assert_equal 200, last_response.status
+ end
+ end
+
private
def build_app
super
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 1557b90d27..33eb034b1c 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -188,7 +188,7 @@ module ApplicationTests
end
end
- etag = "5af83e3196bf99f440f31f2e1a6c9afe".inspect
+ etag = "W/" + "5af83e3196bf99f440f31f2e1a6c9afe".inspect
get "/"
assert_equal 200, last_response.status
diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb
index f8d8a673ae..9ebf163671 100644
--- a/railties/test/application/multiple_applications_test.rb
+++ b/railties/test/application/multiple_applications_test.rb
@@ -36,23 +36,23 @@ module ApplicationTests
end
def test_initialization_of_application_with_previous_config
- application1 = AppTemplate::Application.new(config: Rails.application.config)
- application2 = AppTemplate::Application.new
+ application1 = AppTemplate::Application.create(config: Rails.application.config)
+ application2 = AppTemplate::Application.create
assert_equal Rails.application.config, application1.config, "Creating a new application while setting an initial config should result in the same config"
assert_not_equal Rails.application.config, application2.config, "New applications without setting an initial config should not have the same config"
end
def test_initialization_of_application_with_previous_railties
- application1 = AppTemplate::Application.new(railties: Rails.application.railties)
- application2 = AppTemplate::Application.new
+ application1 = AppTemplate::Application.create(railties: Rails.application.railties)
+ application2 = AppTemplate::Application.create
assert_equal Rails.application.railties, application1.railties
assert_not_equal Rails.application.railties, application2.railties
end
def test_initialize_new_application_with_all_previous_initialization_variables
- application1 = AppTemplate::Application.new(
+ application1 = AppTemplate::Application.create(
config: Rails.application.config,
railties: Rails.application.railties,
routes_reloader: Rails.application.routes_reloader,
@@ -72,26 +72,26 @@ module ApplicationTests
end
def test_rake_tasks_defined_on_different_applications_go_to_the_same_class
- $run_count = 0
+ run_count = 0
application1 = AppTemplate::Application.new
application1.rake_tasks do
- $run_count += 1
+ run_count += 1
end
application2 = AppTemplate::Application.new
application2.rake_tasks do
- $run_count += 1
+ run_count += 1
end
require "#{app_path}/config/environment"
- assert_equal 0, $run_count, "The count should stay at zero without any calls to the rake tasks"
+ assert_equal 0, run_count, "The count should stay at zero without any calls to the rake tasks"
require 'rake'
require 'rake/testtask'
require 'rdoc/task'
Rails.application.load_tasks
- assert_equal 2, $run_count, "Calling a rake task should result in two increments to the count"
+ assert_equal 2, run_count, "Calling a rake task should result in two increments to the count"
end
def test_multiple_applications_can_be_initialized
@@ -100,56 +100,56 @@ module ApplicationTests
def test_initializers_run_on_different_applications_go_to_the_same_class
application1 = AppTemplate::Application.new
- $run_count = 0
+ run_count = 0
AppTemplate::Application.initializer :init0 do
- $run_count += 1
+ run_count += 1
end
application1.initializer :init1 do
- $run_count += 1
+ run_count += 1
end
AppTemplate::Application.new.initializer :init2 do
- $run_count += 1
+ run_count += 1
end
- assert_equal 0, $run_count, "Without loading the initializers, the count should be 0"
+ assert_equal 0, run_count, "Without loading the initializers, the count should be 0"
# Set config.eager_load to false so that an eager_load warning doesn't pop up
AppTemplate::Application.new { config.eager_load = false }.initialize!
- assert_equal 3, $run_count, "There should have been three initializers that incremented the count"
+ assert_equal 3, run_count, "There should have been three initializers that incremented the count"
end
def test_consoles_run_on_different_applications_go_to_the_same_class
- $run_count = 0
- AppTemplate::Application.console { $run_count += 1 }
- AppTemplate::Application.new.console { $run_count += 1 }
+ run_count = 0
+ AppTemplate::Application.console { run_count += 1 }
+ AppTemplate::Application.new.console { run_count += 1 }
- assert_equal 0, $run_count, "Without loading the consoles, the count should be 0"
+ assert_equal 0, run_count, "Without loading the consoles, the count should be 0"
Rails.application.load_console
- assert_equal 2, $run_count, "There should have been two consoles that increment the count"
+ assert_equal 2, run_count, "There should have been two consoles that increment the count"
end
def test_generators_run_on_different_applications_go_to_the_same_class
- $run_count = 0
- AppTemplate::Application.generators { $run_count += 1 }
- AppTemplate::Application.new.generators { $run_count += 1 }
+ run_count = 0
+ AppTemplate::Application.generators { run_count += 1 }
+ AppTemplate::Application.new.generators { run_count += 1 }
- assert_equal 0, $run_count, "Without loading the generators, the count should be 0"
+ assert_equal 0, run_count, "Without loading the generators, the count should be 0"
Rails.application.load_generators
- assert_equal 2, $run_count, "There should have been two generators that increment the count"
+ assert_equal 2, run_count, "There should have been two generators that increment the count"
end
def test_runners_run_on_different_applications_go_to_the_same_class
- $run_count = 0
- AppTemplate::Application.runner { $run_count += 1 }
- AppTemplate::Application.new.runner { $run_count += 1 }
+ run_count = 0
+ AppTemplate::Application.runner { run_count += 1 }
+ AppTemplate::Application.new.runner { run_count += 1 }
- assert_equal 0, $run_count, "Without loading the runners, the count should be 0"
+ assert_equal 0, run_count, "Without loading the runners, the count should be 0"
Rails.application.load_runner
- assert_equal 2, $run_count, "There should have been two runners that increment the count"
+ assert_equal 2, run_count, "There should have been two runners that increment the count"
end
def test_isolate_namespace_on_an_application
diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb
index 701843a6fd..0082ec9cd2 100644
--- a/railties/test/application/rack/logger_test.rb
+++ b/railties/test/application/rack/logger_test.rb
@@ -11,10 +11,12 @@ module ApplicationTests
def setup
build_app
+ add_to_config <<-RUBY
+ config.logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ RUBY
+
require "#{app_path}/config/environment"
super
- @logger = MockLogger.new
- Rails.stubs(:logger).returns(@logger)
end
def teardown
@@ -23,7 +25,7 @@ module ApplicationTests
end
def logs
- @logs ||= @logger.logged(:info).join("\n")
+ @logs ||= Rails.logger.logged(:info).join("\n")
end
test "logger logs proper HTTP GET verb and path" do
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/commands/console_test.rb b/railties/test/commands/console_test.rb
index 1273f9d4c2..4aea3e980f 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -6,7 +6,13 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
include EnvHelpers
class FakeConsole
- def self.start; end
+ def self.started?
+ @started
+ end
+
+ def self.start
+ @started = true
+ end
end
def test_sandbox_option
@@ -25,17 +31,18 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def test_start
- FakeConsole.expects(:start)
start
+
+ assert app.console.started?
assert_match(/Loading \w+ environment \(Rails/, output)
end
def test_start_with_sandbox
- app.expects(:sandbox=).with(true)
- FakeConsole.expects(:start)
-
start ["--sandbox"]
+
+ assert app.console.started?
+ assert app.sandbox
assert_match(/Loading \w+ environment in sandbox \(Rails/, output)
end
@@ -51,9 +58,12 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def test_start_with_debugger
- rails_console = Rails::Console.new(app, parse_arguments(["--debugger"]))
- rails_console.expects(:require_debugger).returns(nil)
+ stubbed_console = Class.new(Rails::Console) do
+ def require_debugger
+ end
+ end
+ rails_console = stubbed_console.new(app, parse_arguments(["--debugger"]))
silence_stream(STDOUT) { rails_console.start }
end
end
@@ -64,7 +74,7 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def test_console_defaults_to_IRB
- app = build_app(console: nil)
+ app = build_app(nil)
assert_equal IRB, Rails::Console.new(app).console
end
@@ -115,8 +125,12 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
- Rails::Console.stubs(:available_environments).returns(['dev'])
- options = Rails::Console.parse_arguments(['dev'])
+ stubbed_console = Class.new(Rails::Console) do
+ def available_environments
+ ['dev']
+ end
+ end
+ options = stubbed_console.parse_arguments(['dev'])
assert_match('dev', options[:environment])
end
@@ -131,15 +145,29 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def app
- @app ||= build_app(console: FakeConsole)
+ @app ||= build_app(FakeConsole)
end
- def build_app(config)
- config = mock("config", config)
- app = mock("app", config: config)
- app.stubs(:sandbox=).returns(nil)
- app.expects(:load_console)
- app
+ def build_app(console)
+ mocked_console = Class.new do
+ attr_reader :sandbox, :console
+
+ def initialize(console)
+ @console = console
+ end
+
+ def config
+ self
+ end
+
+ def sandbox=(arg)
+ @sandbox = arg
+ end
+
+ def load_console
+ end
+ end
+ mocked_console.new(console)
end
def parse_arguments(args)
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index 24db395e6e..ede08e7b86 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'minitest/mock'
require 'rails/commands/dbconsole'
class Rails::DBConsoleTest < ActiveSupport::TestCase
@@ -26,20 +27,21 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
"timeout"=> "3000"
}
}
- app_db_config(config_sample)
- assert_equal Rails::DBConsole.new.config, config_sample["test"]
+ app_db_config(config_sample) do
+ assert_equal Rails::DBConsole.new.config, config_sample["test"]
+ end
end
def test_config_with_no_db_config
- app_db_config(nil)
- assert_raise(ActiveRecord::AdapterNotSpecified) {
- Rails::DBConsole.new.config
- }
+ app_db_config(nil) do
+ assert_raise(ActiveRecord::AdapterNotSpecified) {
+ Rails::DBConsole.new.config
+ }
+ end
end
def test_config_with_database_url_only
ENV['DATABASE_URL'] = 'postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000'
- app_db_config(nil)
expected = {
"adapter" => "postgresql",
"host" => "localhost",
@@ -50,7 +52,10 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
"pool" => "5",
"timeout" => "3000"
}.sort
- assert_equal expected, Rails::DBConsole.new.config.sort
+
+ app_db_config(nil) do
+ assert_equal expected, Rails::DBConsole.new.config.sort
+ end
end
def test_config_choose_database_url_if_exists
@@ -68,8 +73,9 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
"timeout" => "3000"
}
}
- app_db_config(sample_config)
- assert_equal host, Rails::DBConsole.new.config["host"]
+ app_db_config(sample_config) do
+ assert_equal host, Rails::DBConsole.new.config["host"]
+ end
end
def test_env
@@ -78,58 +84,65 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
ENV['RAILS_ENV'] = nil
ENV['RACK_ENV'] = nil
- Rails.stubs(:respond_to?).with(:env).returns(false)
- assert_equal Rails::DBConsole.new.environment, "development"
+ Rails.stub(:respond_to?, false) do
+ assert_equal Rails::DBConsole.new.environment, "development"
- ENV['RACK_ENV'] = "rack_env"
- assert_equal Rails::DBConsole.new.environment, "rack_env"
+ ENV['RACK_ENV'] = "rack_env"
+ assert_equal Rails::DBConsole.new.environment, "rack_env"
- ENV['RAILS_ENV'] = "rails_env"
- assert_equal Rails::DBConsole.new.environment, "rails_env"
+ ENV['RAILS_ENV'] = "rails_env"
+ assert_equal Rails::DBConsole.new.environment, "rails_env"
+ end
ensure
ENV['RAILS_ENV'] = "test"
end
def test_rails_env_is_development_when_argument_is_dev
- Rails::DBConsole.stubs(:available_environments).returns(['development', 'test'])
- options = Rails::DBConsole.new.send(:parse_arguments, ['dev'])
- assert_match('development', options[:environment])
+ dbconsole = Rails::DBConsole.new
+
+ dbconsole.stub(:available_environments, ['development', 'test']) do
+ options = dbconsole.send(:parse_arguments, ['dev'])
+ assert_match('development', options[:environment])
+ end
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
- Rails::DBConsole.stubs(:available_environments).returns(['dev'])
- options = Rails::DBConsole.new.send(:parse_arguments, ['dev'])
- assert_match('dev', options[:environment])
+ dbconsole = Rails::DBConsole.new
+
+ dbconsole.stub(:available_environments, ['dev']) do
+ options = dbconsole.send(:parse_arguments, ['dev'])
+ assert_match('dev', options[:environment])
+ end
end
def test_mysql
- dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], 'db')
start(adapter: 'mysql', database: 'db')
assert !aborted
+ assert_equal [%w[mysql mysql5], 'db'], dbconsole.find_cmd_and_exec_args
end
def test_mysql_full
- dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db')
start(adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8')
assert !aborted
+ assert_equal [%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db'], dbconsole.find_cmd_and_exec_args
end
def test_mysql_include_password
- dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--user=user', '--password=qwerty', 'db')
start({adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}, ['-p'])
assert !aborted
+ assert_equal [%w[mysql mysql5], '--user=user', '--password=qwerty', 'db'], dbconsole.find_cmd_and_exec_args
end
def test_postgresql
- dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
start(adapter: 'postgresql', database: 'db')
assert !aborted
+ assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args
end
def test_postgresql_full
- dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
start(adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432)
assert !aborted
+ assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args
assert_equal 'user', ENV['PGUSER']
assert_equal 'host', ENV['PGHOST']
assert_equal '5432', ENV['PGPORT']
@@ -137,60 +150,60 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_postgresql_include_password
- dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
start({adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}, ['-p'])
assert !aborted
+ assert_equal ['psql', 'db'], dbconsole.find_cmd_and_exec_args
assert_equal 'user', ENV['PGUSER']
assert_equal 'q1w2e3', ENV['PGPASSWORD']
end
def test_sqlite
- dbconsole.expects(:find_cmd_and_exec).with('sqlite', 'db')
start(adapter: 'sqlite', database: 'db')
assert !aborted
+ assert_equal ['sqlite', 'db'], dbconsole.find_cmd_and_exec_args
end
def test_sqlite3
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', Rails.root.join('db.sqlite3').to_s)
start(adapter: 'sqlite3', database: 'db.sqlite3')
assert !aborted
+ assert_equal ['sqlite3', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args
end
def test_sqlite3_mode
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', Rails.root.join('db.sqlite3').to_s)
start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--mode', 'html'])
assert !aborted
+ assert_equal ['sqlite3', '-html', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args
end
def test_sqlite3_header
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', Rails.root.join('db.sqlite3').to_s)
start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--header'])
+ assert_equal ['sqlite3', '-header', Rails.root.join('db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args
end
def test_sqlite3_db_absolute_path
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '/tmp/db.sqlite3')
start(adapter: 'sqlite3', database: '/tmp/db.sqlite3')
assert !aborted
+ assert_equal ['sqlite3', '/tmp/db.sqlite3'], dbconsole.find_cmd_and_exec_args
end
def test_sqlite3_db_without_defined_rails_root
- Rails.stubs(:respond_to?)
- Rails.expects(:respond_to?).with(:root).once.returns(false)
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', Rails.root.join('../config/db.sqlite3').to_s)
- start(adapter: 'sqlite3', database: 'config/db.sqlite3')
- assert !aborted
+ Rails.stub(:respond_to?, false) do
+ start(adapter: 'sqlite3', database: 'config/db.sqlite3')
+ assert !aborted
+ assert_equal ['sqlite3', Rails.root.join('../config/db.sqlite3').to_s], dbconsole.find_cmd_and_exec_args
+ end
end
def test_oracle
- dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user@db')
start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret')
assert !aborted
+ assert_equal ['sqlplus', 'user@db'], dbconsole.find_cmd_and_exec_args
end
def test_oracle_include_password
- dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user/secret@db')
start({adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}, ['-p'])
assert !aborted
+ assert_equal ['sqlplus', 'user/secret@db'], dbconsole.find_cmd_and_exec_args
end
def test_unknown_command_line_client
@@ -223,16 +236,27 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
private
def app_db_config(results)
- Rails.application.config.stubs(:database_configuration).returns(results || {})
+ Rails.application.config.stub(:database_configuration, results || {}) do
+ yield
+ end
end
def dbconsole
- @dbconsole ||= Rails::DBConsole.new(nil)
+ @dbconsole ||= Class.new(Rails::DBConsole) do
+ attr_reader :find_cmd_and_exec_args
+
+ def find_cmd_and_exec(*args)
+ @find_cmd_and_exec_args = args
+ end
+ end.new(nil)
end
def start(config = {}, argv = [])
- dbconsole.stubs(config: config.stringify_keys, arguments: argv)
- capture_abort { dbconsole.start }
+ dbconsole.stub(:config, config.stringify_keys) do
+ dbconsole.stub(:arguments, argv) do
+ capture_abort { dbconsole.start }
+ end
+ end
end
def capture_abort
diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb
index 7970913d21..f46fb748f5 100644
--- a/railties/test/engine_test.rb
+++ b/railties/test/engine_test.rb
@@ -11,4 +11,15 @@ class EngineTest < ActiveSupport::TestCase
assert !engine.routes?
end
+
+ def test_application_can_be_subclassed
+ klass = Class.new(Rails::Application) do
+ attr_reader :hello
+ def initialize
+ @hello = "world"
+ super
+ end
+ end
+ assert_equal "world", klass.instance.hello
+ end
end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 6d6de0fb52..a4337926d1 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -1,6 +1,7 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
require 'env_helpers'
+require 'mocha/setup' # FIXME: stop using mocha
class ActionsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
@@ -242,7 +243,7 @@ class ActionsTest < Rails::Generators::TestCase
protected
def action(*args, &block)
- silence(:stdout){ generator.send(*args, &block) }
+ capture(:stdout){ generator.send(*args, &block) }
end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 2ac5410960..70c439672f 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -1,6 +1,7 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
require 'generators/shared_generator_tests'
+require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_APP_FILES = %w(
.gitignore
@@ -193,20 +194,20 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_config_database_is_added_by_default
run_generator
assert_file "config/database.yml", /sqlite3/
- unless defined?(JRUBY_VERSION)
- assert_gem "sqlite3"
- else
+ if defined?(JRUBY_VERSION)
assert_gem "activerecord-jdbcsqlite3-adapter"
+ else
+ assert_gem "sqlite3"
end
end
def test_config_another_database
run_generator([destination_root, "-d", "mysql"])
assert_file "config/database.yml", /mysql/
- unless defined?(JRUBY_VERSION)
- assert_gem "mysql2"
- else
+ if defined?(JRUBY_VERSION)
assert_gem "activerecord-jdbcmysql-adapter"
+ else
+ assert_gem "mysql2"
end
end
@@ -218,10 +219,10 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_config_postgresql_database
run_generator([destination_root, "-d", "postgresql"])
assert_file "config/database.yml", /postgresql/
- unless defined?(JRUBY_VERSION)
- assert_gem "pg"
- else
+ if defined?(JRUBY_VERSION)
assert_gem "activerecord-jdbcpostgresql-adapter"
+ else
+ assert_gem "pg"
end
end
@@ -250,9 +251,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_gem "activerecord-jdbc-adapter"
end
- def test_config_jdbc_database_when_no_option_given
- if defined?(JRUBY_VERSION)
- run_generator([destination_root])
+ if defined?(JRUBY_VERSION)
+ def test_config_jdbc_database_when_no_option_given
+ run_generator
assert_file "config/database.yml", /sqlite3/
assert_gem "activerecord-jdbcsqlite3-adapter"
end
@@ -294,11 +295,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_inclusion_of_javascript_runtime
- run_generator([destination_root])
+ run_generator
if defined?(JRUBY_VERSION)
assert_gem "therubyrhino"
else
- assert_file "Gemfile", /# gem\s+["']therubyracer["']+, \s+platforms: :ruby$/
+ assert_file "Gemfile", /# gem 'therubyracer', platforms: :ruby/
end
end
@@ -339,7 +340,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_jbuilder
run_generator
- assert_file "Gemfile", /gem 'jbuilder'/
+ assert_gem 'jbuilder'
end
def test_inclusion_of_a_debugger
@@ -350,9 +351,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/debugger/, content)
end
elsif RUBY_VERSION < '2.0.0'
- assert_file "Gemfile", /# gem 'debugger'/
+ assert_gem 'debugger'
else
- assert_file "Gemfile", /# gem 'byebug'/
+ assert_gem 'byebug'
end
end
@@ -397,7 +398,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_new_hash_style
- run_generator [destination_root]
+ run_generator
assert_file "config/initializers/session_store.rb" do |file|
assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file)
end
@@ -418,9 +419,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "foo bar/config/initializers/session_store.rb", /key: '_foo_bar/
end
+ def test_web_console
+ run_generator
+ assert_gem 'web-console'
+ end
+
def test_spring
run_generator
- assert_file "Gemfile", /gem 'spring', \s+group: :development/
+ assert_gem 'spring'
end
def test_spring_binstubs
@@ -487,13 +493,41 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_psych_gem
+ run_generator
+ gem_regex = /gem 'psych',\s+'~> 2.0', \s+platforms: :rbx/
+
+ assert_file "Gemfile" do |content|
+ if defined?(Rubinius)
+ assert_match(gem_regex, content)
+ else
+ assert_no_match(gem_regex, content)
+ end
+ end
+ end
+
+ def test_after_bundle_callback
+ path = 'http://example.org/rails_template'
+ template = %{ after_bundle { run 'echo ran after_bundle' } }
+ 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)
+
+ bundler_first = sequence('bundle, binstubs, after_bundle')
+ generator.expects(:bundle_command).with('install').once.in_sequence(bundler_first)
+ generator.expects(:bundle_command).with('exec spring binstub --all').in_sequence(bundler_first)
+ generator.expects(:run).with('echo ran after_bundle').in_sequence(bundler_first)
+
+ quietly { generator.invoke_all }
+ end
+
protected
def action(*args, &block)
- silence(:stdout) { generator.send(*args, &block) }
+ capture(:stdout) { generator.send(*args, &block) }
end
def assert_gem(gem)
- assert_file "Gemfile", /^gem\s+["']#{gem}["']$/
+ assert_file "Gemfile", /^\s*gem\s+["']#{gem}["']$*/
end
end
diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb
index 31c2d846e2..31e07bc8da 100644
--- a/railties/test/generators/argv_scrubber_test.rb
+++ b/railties/test/generators/argv_scrubber_test.rb
@@ -1,5 +1,5 @@
-require 'active_support/testing/autorun'
require 'active_support/test_case'
+require 'active_support/testing/autorun'
require 'rails/generators/rails/app/app_generator'
require 'tempfile'
diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb
index 28b527cb0e..a7d56dd352 100644
--- a/railties/test/generators/controller_generator_test.rb
+++ b/railties/test/generators/controller_generator_test.rb
@@ -28,13 +28,11 @@ class ControllerGeneratorTest < Rails::Generators::TestCase
def test_invokes_helper
run_generator
assert_file "app/helpers/account_helper.rb"
- assert_file "test/helpers/account_helper_test.rb"
end
def test_does_not_invoke_helper_if_required
run_generator ["account", "--skip-helper"]
assert_no_file "app/helpers/account_helper.rb"
- assert_no_file "test/helpers/account_helper_test.rb"
end
def test_invokes_assets
diff --git a/railties/test/generators/generator_test.rb b/railties/test/generators/generator_test.rb
index b136239795..7871399dd7 100644
--- a/railties/test/generators/generator_test.rb
+++ b/railties/test/generators/generator_test.rb
@@ -1,5 +1,5 @@
-require 'active_support/testing/autorun'
require 'active_support/test_case'
+require 'active_support/testing/autorun'
require 'rails/generators/app_base'
module Rails
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 77ec2f1c0c..e7990de754 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -7,7 +7,7 @@ module Rails
class << self
remove_possible_method :root
def root
- @root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures'))
+ @root ||= File.expand_path('../../fixtures', __FILE__)
end
end
end
@@ -41,4 +41,12 @@ module GeneratorsTestHelper
FileUtils.mkdir_p(destination)
FileUtils.cp routes, destination
end
+
+ def quietly
+ silence_stream(STDOUT) do
+ silence_stream(STDERR) do
+ yield
+ end
+ end
+ end
end
diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb
index 81d4fcb129..add04f21a4 100644
--- a/railties/test/generators/helper_generator_test.rb
+++ b/railties/test/generators/helper_generator_test.rb
@@ -13,26 +13,11 @@ class HelperGeneratorTest < Rails::Generators::TestCase
assert_file "app/helpers/admin_helper.rb", /module AdminHelper/
end
- def test_invokes_default_test_framework
- run_generator
- assert_file "test/helpers/admin_helper_test.rb", /class AdminHelperTest < ActionView::TestCase/
- end
-
- def test_logs_if_the_test_framework_cannot_be_found
- content = run_generator ["admin", "--test-framework=rspec"]
- assert_match(/rspec \[not found\]/, content)
- end
-
def test_check_class_collision
content = capture(:stderr){ run_generator ["object"] }
assert_match(/The name 'ObjectHelper' is either already used in your application or reserved/, content)
end
- def test_check_class_collision_on_tests
- content = capture(:stderr){ run_generator ["another_object"] }
- assert_match(/The name 'AnotherObjectHelperTest' is either already used in your application or reserved/, content)
- end
-
def test_namespaced_and_not_namespaced_helpers
run_generator ["products"]
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index ac5cfff229..4199e00b0d 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -1,5 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator'
+require 'mocha/setup' # FIXME: stop using mocha
# Mock out what we need from AR::Base.
module ActiveRecord
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index d677c21f15..7eeb084eab 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -47,7 +47,6 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
def test_helper_is_also_namespaced
run_generator
assert_file "app/helpers/test_app/account_helper.rb", /module TestApp/, / module AccountHelper/
- assert_file "test/helpers/test_app/account_helper_test.rb", /module TestApp/, / class AccountHelperTest/
end
def test_invokes_default_test_framework
@@ -229,7 +228,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Helpers
assert_file "app/helpers/test_app/product_lines_helper.rb"
- assert_file "test/helpers/test_app/product_lines_helper_test.rb"
# Stylesheets
assert_file "app/assets/stylesheets/scaffold.css"
@@ -260,7 +258,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Helpers
assert_no_file "app/helpers/test_app/product_lines_helper.rb"
- assert_no_file "test/helpers/test_app/product_lines_helper_test.rb"
# Stylesheets (should not be removed)
assert_file "app/assets/stylesheets/scaffold.css"
@@ -297,7 +294,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Helpers
assert_file "app/helpers/test_app/admin/roles_helper.rb"
- assert_file "test/helpers/test_app/admin/roles_helper_test.rb"
# Stylesheets
assert_file "app/assets/stylesheets/scaffold.css"
@@ -329,7 +325,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Helpers
assert_no_file "app/helpers/test_app/admin/roles_helper.rb"
- assert_no_file "test/helpers/test_app/admin/roles_helper_test.rb"
# Stylesheets (should not be removed)
assert_file "app/assets/stylesheets/scaffold.css"
@@ -366,7 +361,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Helpers
assert_file "app/helpers/test_app/admin/user/special/roles_helper.rb"
- assert_file "test/helpers/test_app/admin/user/special/roles_helper_test.rb"
# Stylesheets
assert_file "app/assets/stylesheets/scaffold.css"
@@ -397,7 +391,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Helpers
assert_no_file "app/helpers/test_app/admin/user/special/roles_helper.rb"
- assert_no_file "test/helpers/test_app/admin/user/special/roles_helper_test.rb"
# Stylesheets (should not be removed)
assert_file "app/assets/stylesheets/scaffold.css"
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 7180efee41..985644e8af 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -1,6 +1,7 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/plugin/plugin_generator'
require 'generators/shared_generator_tests'
+require 'mocha/setup' # FIXME: stop using mocha
DEFAULT_PLUGIN_FILES = %w(
.gitignore
@@ -94,7 +95,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
end
def test_generating_adds_dummy_app_without_javascript_and_assets_deps
- run_generator [destination_root]
+ run_generator
assert_file "test/dummy/app/assets/stylesheets/application.css"
@@ -334,7 +335,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
Object.const_set('APP_PATH', Rails.root)
FileUtils.touch gemfile_path
- run_generator [destination_root]
+ run_generator
assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/
ensure
@@ -375,19 +376,19 @@ class PluginGeneratorTest < Rails::Generators::TestCase
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]
+ run_generator
assert_file "bukkits.gemspec" do |contents|
- assert_match(/#{Regexp.escape(name)}/, contents)
- assert_match(/#{Regexp.escape(email)}/, contents)
+ assert_match name, contents
+ assert_match 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]
+ run_generator
assert_file "MIT-LICENSE" do |contents|
- assert_match(/#{Regexp.escape(name)}/, contents)
+ assert_match name, contents
end
end
@@ -397,11 +398,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, '--skip-git']
assert_file "MIT-LICENSE" do |contents|
- assert_match(/#{Regexp.escape(name)}/, contents)
+ assert_match name, contents
end
assert_file "bukkits.gemspec" do |contents|
- assert_match(/#{Regexp.escape(name)}/, contents)
- assert_match(/#{Regexp.escape(email)}/, contents)
+ assert_match name, contents
+ assert_match email, contents
end
end
@@ -415,10 +416,10 @@ protected
end
def assert_match_sqlite3(contents)
- unless defined?(JRUBY_VERSION)
- assert_match(/group :development do\n gem 'sqlite3'\nend/, contents)
- else
+ if defined?(JRUBY_VERSION)
assert_match(/group :development do\n gem 'activerecord-jdbcsqlite3-adapter'\nend/, contents)
+ else
+ assert_match(/group :development do\n gem 'sqlite3'\nend/, contents)
end
end
end
diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb
index dcdff22152..581d80d60e 100644
--- a/railties/test/generators/resource_generator_test.rb
+++ b/railties/test/generators/resource_generator_test.rb
@@ -36,7 +36,6 @@ class ResourceGeneratorTest < Rails::Generators::TestCase
assert_file "test/controllers/accounts_controller_test.rb", /class AccountsControllerTest < ActionController::TestCase/
assert_file "app/helpers/accounts_helper.rb", /module AccountsHelper/
- assert_file "test/helpers/accounts_helper_test.rb", /class AccountsHelperTest < ActionView::TestCase/
end
def test_resource_controller_with_actions
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 46eacd2845..ca972a3bdd 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -81,7 +81,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
def test_helper_are_invoked_with_a_pluralized_name
run_generator
assert_file "app/helpers/users_helper.rb", /module UsersHelper/
- assert_file "test/helpers/users_helper_test.rb", /class UsersHelperTest < ActionView::TestCase/
end
def test_views_are_generated
@@ -126,7 +125,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
def test_skip_helper_if_required
run_generator ["User", "name:string", "age:integer", "--no-helper"]
assert_no_file "app/helpers/users_helper.rb"
- assert_no_file "test/helpers/users_helper_test.rb"
end
def test_skip_layout_if_required
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 524bbde2b7..637bde2a44 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -70,7 +70,6 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Helpers
assert_file "app/helpers/product_lines_helper.rb"
- assert_file "test/helpers/product_lines_helper_test.rb"
# Assets
assert_file "app/assets/stylesheets/scaffold.css"
@@ -114,7 +113,6 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Helpers
assert_no_file "app/helpers/product_lines_helper.rb"
- assert_no_file "test/helpers/product_lines_helper_test.rb"
# Assets
assert_file "app/assets/stylesheets/scaffold.css", /:visited/
@@ -182,7 +180,6 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Helpers
assert_file "app/helpers/admin/roles_helper.rb"
- assert_file "test/helpers/admin/roles_helper_test.rb"
# Assets
assert_file "app/assets/stylesheets/scaffold.css", /:visited/
@@ -216,7 +213,6 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Helpers
assert_no_file "app/helpers/admin/roles_helper.rb"
- assert_no_file "test/helpers/admin/roles_helper_test.rb"
# Assets
assert_file "app/assets/stylesheets/scaffold.css"
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 0b4edafaca..b5765c391e 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -1,6 +1,7 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/model/model_generator'
require 'rails/generators/test_unit/model/model_generator'
+require 'mocha/setup' # FIXME: stop using mocha
class GeneratorsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 6c50911666..92d6a1729c 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -291,6 +291,33 @@ class ActiveSupport::TestCase
include TestHelpers::Paths
include TestHelpers::Rack
include TestHelpers::Generation
+
+ private
+
+ def capture(stream)
+ stream = stream.to_s
+ captured_stream = Tempfile.new(stream)
+ stream_io = eval("$#{stream}")
+ origin_stream = stream_io.dup
+ stream_io.reopen(captured_stream)
+
+ yield
+
+ stream_io.rewind
+ return captured_stream.read
+ ensure
+ captured_stream.close
+ captured_stream.unlink
+ stream_io.reopen(origin_stream)
+ end
+
+ def quietly
+ silence_stream(STDOUT) do
+ silence_stream(STDERR) do
+ yield
+ end
+ end
+ end
end
# Create a scope and build a fixture rails app
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
new file mode 100644
index 0000000000..13bf29d3c3
--- /dev/null
+++ b/railties/test/path_generation_test.rb
@@ -0,0 +1,88 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'active_support/core_ext/object/with_options'
+require 'active_support/core_ext/object/json'
+require 'rails'
+require 'rails/application'
+
+ROUTING = ActionDispatch::Routing
+
+class PathGenerationTest < ActiveSupport::TestCase
+ attr_reader :app
+
+ class TestSet < ROUTING::RouteSet
+ def initialize(block)
+ @block = block
+ super()
+ end
+
+ class Dispatcher < ROUTING::RouteSet::Dispatcher
+ def initialize(defaults, set, block)
+ super(defaults)
+ @block = block
+ @set = set
+ end
+
+ def controller_reference(controller_param)
+ block = @block
+ set = @set
+ Class.new(ActionController::Base) {
+ include set.url_helpers
+ define_method(:process) { |name| block.call(self) }
+ def to_a; [200, {}, []]; end
+ }
+ end
+ end
+
+ def dispatcher defaults
+ TestSet::Dispatcher.new defaults, self, @block
+ end
+ end
+
+ def send_request(uri_or_host, method, path, script_name = nil)
+ host = uri_or_host.host unless path
+ path ||= uri_or_host.path
+
+ params = {'PATH_INFO' => path,
+ 'REQUEST_METHOD' => method,
+ 'HTTP_HOST' => host }
+
+ params['SCRIPT_NAME'] = script_name if script_name
+
+ status, headers, body = app.call(params)
+ new_body = []
+ body.each { |part| new_body << part }
+ body.close if body.respond_to? :close
+ [status, headers, new_body]
+ end
+
+ def test_original_script_name
+ original_logger = Rails.logger
+ Rails.logger = Logger.new nil
+
+ app = Class.new(Rails::Application) {
+ attr_accessor :controller
+ def initialize
+ super
+ app = self
+ @routes = TestSet.new ->(c) { app.controller = c }
+ secrets.secret_key_base = "foo"
+ secrets.secret_token = "foo"
+ end
+ def app; routes; end
+ }
+
+ @app = app
+ app.routes.draw { resource :blogs }
+
+ url = URI("http://example.org/blogs")
+
+ send_request(url, 'GET', nil, '/FOO')
+ assert_equal '/FOO/blogs', app.instance.controller.blogs_path
+
+ send_request(url, 'GET', nil)
+ assert_equal '/blogs', app.instance.controller.blogs_path
+ ensure
+ Rails.logger = original_logger
+ end
+end
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index ed4559ec6f..1aeb9ec339 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'rails/paths'
+require 'mocha/setup' # FIXME: stop using mocha
class PathsTest < ActiveSupport::TestCase
def setup
diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb
index 6ebd47fff9..fcc79b57fb 100644
--- a/railties/test/rack_logger_test.rb
+++ b/railties/test/rack_logger_test.rb
@@ -39,11 +39,11 @@ module Rails
def setup
@subscriber = Subscriber.new
@notifier = ActiveSupport::Notifications.notifier
- notifier.subscribe 'request.action_dispatch', subscriber
+ @subscription = notifier.subscribe 'request.action_dispatch', subscriber
end
def teardown
- notifier.unsubscribe subscriber
+ notifier.unsubscribe @subscription
end
def test_notification
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index a9b237d0a5..8d61af4972 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'mocha/setup' # FIXME: stop using mocha
module ActionController
class Base