aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--Gemfile3
-rw-r--r--README.md2
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--Rakefile10
-rw-r--r--actionmailer/CHANGELOG.md5
-rw-r--r--actionmailer/README.rdoc4
-rw-r--r--actionmailer/lib/action_mailer/base.rb27
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb7
-rw-r--r--actionmailer/test/delivery_methods_test.rb3
-rw-r--r--actionmailer/test/fixtures/raw_email1020
-rw-r--r--actionmailer/test/fixtures/raw_email1232
-rw-r--r--actionmailer/test/fixtures/raw_email1329
-rw-r--r--actionmailer/test/fixtures/raw_email2114
-rw-r--r--actionmailer/test/fixtures/raw_email370
-rw-r--r--actionmailer/test/fixtures/raw_email459
-rw-r--r--actionmailer/test/fixtures/raw_email519
-rw-r--r--actionmailer/test/fixtures/raw_email620
-rw-r--r--actionmailer/test/fixtures/raw_email766
-rw-r--r--actionmailer/test/fixtures/raw_email847
-rw-r--r--actionmailer/test/fixtures/raw_email928
-rw-r--r--actionmailer/test/fixtures/raw_email_quoted_with_0d0a14
-rw-r--r--actionmailer/test/fixtures/raw_email_with_invalid_characters_in_content_type104
-rw-r--r--actionmailer/test/fixtures/raw_email_with_nested_attachment100
-rw-r--r--actionmailer/test/fixtures/raw_email_with_partially_quoted_subject14
-rw-r--r--actionmailer/test/log_subscriber_test.rb9
-rw-r--r--actionpack/CHANGELOG.md126
-rw-r--r--actionpack/actionpack.gemspec9
-rw-r--r--actionpack/lib/abstract_controller/base.rb5
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb59
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb16
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb2
-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/helpers.rb6
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb5
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb39
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb3
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb56
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb3
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb17
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb35
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb50
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb7
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb25
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb13
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb20
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb26
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb20
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb18
-rw-r--r--actionpack/test/controller/caching_test.rb4
-rw-r--r--actionpack/test/controller/filters_test.rb2
-rw-r--r--actionpack/test/controller/flash_test.rb12
-rw-r--r--actionpack/test/controller/helper_test.rb6
-rw-r--r--actionpack/test/controller/mime/respond_with_test.rb15
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb15
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_test.rb15
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb15
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb10
-rw-r--r--actionpack/test/controller/routing_test.rb4
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb100
-rw-r--r--actionpack/test/dispatch/request/session_test.rb17
-rw-r--r--actionpack/test/dispatch/routing_test.rb13
-rw-r--r--actionpack/test/fixtures/helpers/abc_helper.rb2
-rw-r--r--actionpack/test/fixtures/helpers/helpery_test_helper.rb5
-rw-r--r--actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb0
-rw-r--r--actionpack/test/journey/router/utils_test.rb8
-rw-r--r--actionpack/test/lib/controller/fake_models.rb95
-rw-r--r--actionview/CHANGELOG.md47
-rw-r--r--actionview/RUNNING_UNIT_TESTS.rdoc2
-rw-r--r--actionview/Rakefile2
-rw-r--r--actionview/actionview.gemspec2
-rw-r--r--actionview/lib/action_view/digestor.rb17
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb17
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb3
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb1
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb3
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb14
-rw-r--r--actionview/lib/action_view/railtie.rb4
-rw-r--r--actionview/lib/action_view/rendering.rb41
-rw-r--r--actionview/lib/action_view/tasks/dependencies.rake17
-rw-r--r--actionview/lib/action_view/template.rb4
-rw-r--r--actionview/test/actionpack/controller/view_paths_test.rb (renamed from actionview/test/lib/controller/view_paths_test.rb)0
-rw-r--r--actionview/test/fixtures/helpers/abc_helper.rb2
-rw-r--r--actionview/test/fixtures/helpers/fun/games_helper.rb5
-rw-r--r--actionview/test/fixtures/helpers/fun/pdf_helper.rb5
-rw-r--r--actionview/test/fixtures/helpers/just_me_helper.rb3
-rw-r--r--actionview/test/fixtures/helpers/me_too_helper.rb3
-rw-r--r--actionview/test/lib/controller/fake_controllers.rb35
-rw-r--r--actionview/test/template/digestor_test.rb55
-rw-r--r--actionview/test/template/form_collections_helper_test.rb15
-rw-r--r--actionview/test/template/form_helper_test.rb48
-rw-r--r--actionview/test/template/form_options_helper_test.rb36
-rw-r--r--actionview/test/template/resolver_patterns_test.rb2
-rw-r--r--actionview/test/template/tag_helper_test.rb4
-rw-r--r--actionview/test/template/text_helper_test.rb3
-rw-r--r--actionview/test/template/url_helper_test.rb8
-rw-r--r--activemodel/CHANGELOG.md15
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/examples/validations.rb1
-rw-r--r--activemodel/lib/active_model/dirty.rb40
-rw-r--r--activemodel/lib/active_model/secure_password.rb7
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb16
-rw-r--r--activemodel/test/cases/dirty_test.rb20
-rw-r--r--activemodel/test/cases/secure_password_test.rb8
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb2
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb14
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb22
-rw-r--r--activemodel/test/models/topic.rb2
-rw-r--r--activerecord/CHANGELOG.md306
-rw-r--r--activerecord/lib/active_record.rb4
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb42
-rw-r--r--activerecord/lib/active_record/associations/association.rb9
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb20
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb81
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb49
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb41
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb129
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb16
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb27
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb6
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb56
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb16
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb358
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb92
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb14
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb62
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb13
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb117
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb53
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb65
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/singular_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb65
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb42
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb15
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb15
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb4
-rw-r--r--activerecord/lib/active_record/autosave_association.rb398
-rw-r--r--activerecord/lib/active_record/callbacks.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb58
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb62
-rw-r--r--activerecord/lib/active_record/core.rb31
-rw-r--r--activerecord/lib/active_record/fixtures.rb109
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb1
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb6
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb13
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb6
-rw-r--r--activerecord/lib/active_record/null_relation.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb24
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/railties/console_sandbox.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake1
-rw-r--r--activerecord/lib/active_record/reflection.rb52
-rw-r--r--activerecord/lib/active_record/relation.rb9
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb25
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb17
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb31
-rw-r--r--activerecord/lib/active_record/relation/merger.rb8
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb57
-rw-r--r--activerecord/lib/active_record/result.rb20
-rw-r--r--activerecord/lib/active_record/sanitization.rb14
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb22
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/store.rb63
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb3
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb3
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record.rb10
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb14
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb32
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb40
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/statement_pool_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb45
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb22
-rw-r--r--activerecord/test/cases/associations/eager_test.rb24
-rw-r--r--activerecord/test/cases/associations/extension_test.rb3
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb76
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb130
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb13
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb5
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb25
-rw-r--r--activerecord/test/cases/associations/join_dependency_test.rb8
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb7
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb2
-rw-r--r--activerecord/test/cases/autosave_association_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb144
-rw-r--r--activerecord/test/cases/column_test.rb8
-rw-r--r--activerecord/test/cases/date_time_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb42
-rw-r--r--activerecord/test/cases/finder_test.rb16
-rw-r--r--activerecord/test/cases/fixtures_test.rb46
-rw-r--r--activerecord/test/cases/forbidden_attributes_protection_test.rb5
-rw-r--r--activerecord/test/cases/helper.rb80
-rw-r--r--activerecord/test/cases/inheritance_test.rb6
-rw-r--r--activerecord/test/cases/integration_test.rb13
-rw-r--r--activerecord/test/cases/locking_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb11
-rw-r--r--activerecord/test/cases/modules_test.rb1
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb160
-rw-r--r--activerecord/test/cases/persistence_test.rb15
-rw-r--r--activerecord/test/cases/quoting_test.rb10
-rw-r--r--activerecord/test/cases/reflection_test.rb16
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb98
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb148
-rw-r--r--activerecord/test/cases/relation_test.rb143
-rw-r--r--activerecord/test/cases/relations_test.rb76
-rw-r--r--activerecord/test/cases/sanitize_test.rb17
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb7
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb20
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb22
-rw-r--r--activerecord/test/cases/store_test.rb12
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb9
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb7
-rw-r--r--activerecord/test/cases/transactions_test.rb34
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb20
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb14
-rw-r--r--activerecord/test/fixtures/sponsors.yml2
-rw-r--r--activerecord/test/models/author.rb10
-rw-r--r--activerecord/test/models/auto_id.rb4
-rw-r--r--activerecord/test/models/cake_designer.rb3
-rw-r--r--activerecord/test/models/car.rb3
-rw-r--r--activerecord/test/models/chef.rb3
-rw-r--r--activerecord/test/models/citation.rb3
-rw-r--r--activerecord/test/models/club.rb1
-rw-r--r--activerecord/test/models/company.rb13
-rw-r--r--activerecord/test/models/contract.rb1
-rw-r--r--activerecord/test/models/department.rb4
-rw-r--r--activerecord/test/models/developer.rb2
-rw-r--r--activerecord/test/models/drink_designer.rb3
-rw-r--r--activerecord/test/models/hotel.rb6
-rw-r--r--activerecord/test/models/member.rb1
-rw-r--r--activerecord/test/models/membership.rb5
-rw-r--r--activerecord/test/models/movie.rb4
-rw-r--r--activerecord/test/models/post.rb4
-rw-r--r--activerecord/test/models/project.rb1
-rw-r--r--activerecord/test/models/topic.rb1
-rw-r--r--activerecord/test/schema/schema.rb16
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb7
-rw-r--r--activesupport/CHANGELOG.md59
-rwxr-xr-xactivesupport/bin/generate_tables6
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb4
-rw-r--r--activesupport/lib/active_support/callbacks.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb217
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb30
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb24
-rw-r--r--activesupport/lib/active_support/dependencies.rb13
-rw-r--r--activesupport/lib/active_support/duration.rb10
-rw-r--r--activesupport/lib/active_support/json/decoding.rb11
-rw-r--r--activesupport/lib/active_support/json/encoding.rb197
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb3
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb7
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb11
-rw-r--r--activesupport/lib/active_support/number_helper.rb8
-rw-r--r--activesupport/lib/active_support/subscriber.rb27
-rw-r--r--activesupport/lib/active_support/test_case.rb2
-rw-r--r--activesupport/test/caching_test.rb19
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb1
-rw-r--r--activesupport/test/core_ext/duration_test.rb3
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb18
-rw-r--r--activesupport/test/core_ext/object/json_test.rb9
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb22
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/thread_test.rb13
-rw-r--r--activesupport/test/dependencies_test.rb11
-rw-r--r--activesupport/test/descendants_tracker_without_autoloading_test.rb10
-rw-r--r--activesupport/test/json/decoding_test.rb25
-rw-r--r--activesupport/test/json/encoding_test.rb8
-rw-r--r--activesupport/test/ordered_hash_test.rb2
-rw-r--r--activesupport/test/subscriber_test.rb40
-rw-r--r--activesupport/test/transliterate_test.rb6
-rw-r--r--activesupport/test/xml_mini_test.rb12
-rw-r--r--guides/CHANGELOG.md4
-rw-r--r--guides/bug_report_templates/active_record_gem.rb2
-rw-r--r--guides/code/getting_started/Gemfile2
-rw-r--r--guides/source/3_0_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md14
-rw-r--r--guides/source/4_0_release_notes.md24
-rw-r--r--guides/source/_welcome.html.erb2
-rw-r--r--guides/source/action_controller_overview.md46
-rw-r--r--guides/source/action_mailer_basics.md30
-rw-r--r--guides/source/action_view_overview.md4
-rw-r--r--guides/source/active_model_basics.md2
-rw-r--r--guides/source/active_record_basics.md24
-rw-r--r--guides/source/active_record_callbacks.md22
-rw-r--r--guides/source/active_record_querying.md55
-rw-r--r--guides/source/active_record_validations.md6
-rw-r--r--guides/source/active_support_core_extensions.md75
-rw-r--r--guides/source/asset_pipeline.md24
-rw-r--r--guides/source/association_basics.md23
-rw-r--r--guides/source/caching_with_rails.md4
-rw-r--r--guides/source/command_line.md19
-rw-r--r--guides/source/configuring.md27
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md17
-rw-r--r--guides/source/debugging_rails_applications.md4
-rw-r--r--guides/source/documents.yaml7
-rw-r--r--guides/source/engines.md15
-rw-r--r--guides/source/form_helpers.md10
-rw-r--r--guides/source/generators.md35
-rw-r--r--guides/source/getting_started.md386
-rw-r--r--guides/source/i18n.md33
-rw-r--r--guides/source/initialization.md34
-rw-r--r--guides/source/maintenance_policy.md56
-rw-r--r--guides/source/migrations.md43
-rw-r--r--guides/source/plugins.md24
-rw-r--r--guides/source/rails_on_rack.md6
-rw-r--r--guides/source/routing.md187
-rw-r--r--guides/source/security.md8
-rw-r--r--guides/source/testing.md49
-rw-r--r--guides/source/upgrading_ruby_on_rails.md3
-rw-r--r--rails.gemspec1
-rw-r--r--railties/CHANGELOG.md47
-rw-r--r--railties/lib/rails/api/task.rb12
-rw-r--r--railties/lib/rails/app_rails_loader.rb4
-rw-r--r--railties/lib/rails/cli.rb1
-rw-r--r--railties/lib/rails/commands/application.rb4
-rw-r--r--railties/lib/rails/commands/commands_tasks.rb4
-rw-r--r--railties/lib/rails/commands/server.rb6
-rw-r--r--railties/lib/rails/configuration.rb4
-rw-r--r--railties/lib/rails/engine.rb9
-rw-r--r--railties/lib/rails/generators/app_base.rb161
-rw-r--r--railties/lib/rails/generators/base.rb1
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb84
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile30
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml2
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile15
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb25
-rw-r--r--railties/lib/rails/generators/testing/assertions.rb2
-rw-r--r--railties/lib/rails/paths.rb31
-rw-r--r--railties/lib/rails/rack/logger.rb7
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/test/application/assets_test.rb4
-rw-r--r--railties/test/application/basic_rendering_test.rb62
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb10
-rw-r--r--railties/test/application/middleware_test.rb6
-rw-r--r--railties/test/application/multiple_applications_test.rb2
-rw-r--r--railties/test/commands/server_test.rb58
-rw-r--r--railties/test/env_helpers.rb4
-rw-r--r--railties/test/generators/app_generator_test.rb37
-rw-r--r--railties/test/generators/argv_scrubber_test.rb136
-rw-r--r--railties/test/generators/named_base_test.rb19
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb9
-rw-r--r--railties/test/generators/shared_generator_tests.rb5
-rw-r--r--railties/test/paths_test.rb2
398 files changed, 6172 insertions, 4090 deletions
diff --git a/.travis.yml b/.travis.yml
index ad47164b7e..033606f9d9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ before_install:
rvm:
- 1.9.3
- 2.0.0
- - jruby-19mode
+ - jruby-head
- rbx-19mode
env:
- "GEM=railties"
@@ -16,7 +16,7 @@ env:
- "GEM=ar:postgresql"
matrix:
allow_failures:
- - rvm: jruby-19mode
+ - rvm: jruby-head
- rvm: rbx-19mode
notifications:
email: false
diff --git a/Gemfile b/Gemfile
index 94eec28867..fbb0e0fd49 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,10 +8,11 @@ gemspec
gem 'mocha', '~> 0.14', require: false
gem 'rack-cache', '~> 1.2'
-gem 'bcrypt-ruby', '~> 3.1.0'
+gem 'bcrypt-ruby', '~> 3.1.2'
gem 'jquery-rails', '~> 2.2.0'
gem 'turbolinks'
gem 'coffee-rails', '~> 4.0.0'
+gem 'arel', github: 'rails/arel'
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
diff --git a/README.md b/README.md
index 95a66aea28..5a68a166be 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ independently outside Rails.
Run with `--help` or `-h` for options.
-4. Go to http://localhost:3000 and you'll see: "Welcome aboard: You're riding Ruby on Rails!"
+4. Using a browser, go to http://localhost:3000 and you'll see: "Welcome aboard: You're riding Ruby on Rails!"
5. Follow the guidelines to start developing your application. You may find
the following resources handy:
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index 6f8c79eef2..aafbd25486 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -113,7 +113,7 @@ what to do in case anything goes wrong:
$ git tag -m'tagging rc release' v3.0.10.rc1
$ git push
$ git push --tags
- $ for i in $(ls dist); do gem push $i; done
+ $ for i in $(ls pkg); do gem push $i; done
=== Send Rails release announcements
diff --git a/Rakefile b/Rakefile
index 08473ba216..07d44fc94b 100644
--- a/Rakefile
+++ b/Rakefile
@@ -36,15 +36,7 @@ task :smoke do
end
desc "Install gems for all projects."
-task :install => :build do
- version = File.read("RAILS_VERSION").strip
- (PROJECTS - ["railties"]).each do |project|
- puts "INSTALLING #{project}"
- system("gem install #{project}/pkg/#{project}-#{version}.gem --local --no-ri --no-rdoc")
- end
- system("gem install railties/pkg/railties-#{version}.gem --local --no-ri --no-rdoc")
- system("gem install pkg/rails-#{version}.gem --local --no-ri --no-rdoc")
-end
+task :install => "all:install"
desc "Generate documentation for the Rails framework"
if ENV['EDGE']
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 1659696bfb..d84b95e6e9 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Instrument the generation of Action Mailer messages. The time it takes to
+ generate a message is written to the log.
+
+ *Daniel Schierbeck*
+
* invoke mailer defaults as procs only if they are procs, do not convert
with to_proc. That an object is convertible to a proc does not mean it's
meant to be always used as a proc. Fixes #11533
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index a14a6ba18f..96dd0b1a2e 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -61,9 +61,7 @@ generated would look like this:
Thank you for signing up!
-In previous version of Rails you would call <tt>create_method_name</tt> and
-<tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface - you
-simply call the method and optionally call +deliver+ on the return value.
+In order to send mails, you simply call the method and then call +deliver+ on the return value.
Calling the method returns a Mail Message object:
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 9dcd4bf554..becd4126f4 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -373,6 +373,8 @@ module ActionMailer
include AbstractController::AssetPaths
include AbstractController::Callbacks
+ self.protected_instance_variables = [:@_action_has_layout]
+
helper ActionMailer::MailHelper
private_class_method :new #:nodoc:
@@ -385,10 +387,6 @@ module ActionMailer
parts_order: [ "text/plain", "text/enriched", "text/html" ]
}.freeze
- def self.default_protected_instance_vars
- super.concat [:@_action_has_layout]
- end
-
class << self
# Register one or more Observers which will be notified when mail is delivered.
def register_observers(*observers)
@@ -513,11 +511,18 @@ module ActionMailer
process(method_name, *args) if method_name
end
- def process(*args) #:nodoc:
- lookup_context.skip_default_locale!
+ def process(method_name, *args) #:nodoc:
+ payload = {
+ :mailer => self.class.name,
+ :action => method_name
+ }
- super
- @_message = NullMail.new unless @_mail_was_called
+ ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
+ lookup_context.skip_default_locale!
+
+ super
+ @_message = NullMail.new unless @_mail_was_called
+ end
end
class NullMail #:nodoc:
@@ -687,9 +692,9 @@ module ActionMailer
content_type = headers[:content_type]
# Call all the procs (if any)
- class_default = self.class.default
- default_values = class_default.merge(class_default) do |k,v|
- v.is_a?(Proc) ? instance_eval(&v) : v
+ default_values = {}
+ self.class.default.each do |k,v|
+ default_values[k] = v.is_a?(Proc) ? instance_eval(&v) : v
end
# Handle defaults
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index 9a1a27c8ed..aedcd81e52 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -64,7 +64,7 @@ module ActionMailer
raise "Delivery method cannot be nil"
when Symbol
if klass = delivery_methods[method]
- mail.delivery_method(klass,(send(:"#{method}_settings") || {}).merge!(options || {}))
+ mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {}))
else
raise "Invalid delivery method #{method.inspect}"
end
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index 8467d45986..eb6fb11d81 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -19,6 +19,13 @@ module ActionMailer
debug(event.payload[:mail])
end
+ # An email was generated.
+ def process(event)
+ mailer = event.payload[:mailer]
+ action = event.payload[:action]
+ debug("\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms")
+ end
+
# Use the logger configured for ActionMailer::Base
def logger
ActionMailer::Base.logger
diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb
index 61a037ea18..20412c7bb2 100644
--- a/actionmailer/test/delivery_methods_test.rb
+++ b/actionmailer/test/delivery_methods_test.rb
@@ -152,6 +152,9 @@ class MailDeliveryTest < ActiveSupport::TestCase
assert_equal "overridden", delivery_method_instance.settings[:user_name]
assert_equal "somethingobtuse", delivery_method_instance.settings[:password]
assert_equal delivery_method_instance.settings.merge(overridden_options), delivery_method_instance.settings
+
+ # make sure that overriding delivery method options per mail instance doesn't affect the Base setting
+ assert_equal settings, ActionMailer::Base.smtp_settings
end
test "non registered delivery methods raises errors" do
diff --git a/actionmailer/test/fixtures/raw_email10 b/actionmailer/test/fixtures/raw_email10
deleted file mode 100644
index edad5ccff1..0000000000
--- a/actionmailer/test/fixtures/raw_email10
+++ /dev/null
@@ -1,20 +0,0 @@
-Return-Path: <xxx@xxxx.xxx>
-Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
-Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
-Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
-Date: Tue, 10 May 2005 15:27:03 -0500
-From: xxx@xxxx.xxx
-Sender: xxx@xxxx.xxx
-To: xxxxxxxxxxx@xxxx.xxxx.xxx
-Message-Id: <xxx@xxxx.xxx>
-X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
-Delivered-To: xxx@xxxx.xxx
-Importance: normal
-Content-Type: text/plain; charset=X-UNKNOWN
-
-Test test. Hi. Waving. m
-
-----------------------------------------------------------------
-Sent via Bell Mobility's Text Messaging service.
-Envoyé par le service de messagerie texte de Bell Mobilité.
-----------------------------------------------------------------
diff --git a/actionmailer/test/fixtures/raw_email12 b/actionmailer/test/fixtures/raw_email12
deleted file mode 100644
index 2cd31720d3..0000000000
--- a/actionmailer/test/fixtures/raw_email12
+++ /dev/null
@@ -1,32 +0,0 @@
-Mime-Version: 1.0 (Apple Message framework v730)
-Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
-Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
-From: foo@example.com
-Subject: testing
-Date: Mon, 6 Jun 2005 22:21:22 +0200
-To: blah@example.com
-
-
---Apple-Mail-13-196941151
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/plain;
- charset=ISO-8859-1;
- delsp=yes;
- format=flowed
-
-This is the first part.
-
---Apple-Mail-13-196941151
-Content-Type: image/jpeg
-Content-Transfer-Encoding: base64
-Content-Location: Photo25.jpg
-Content-ID: <qbFGyPQAS8>
-Content-Disposition: inline
-
-jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
-ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
-QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
-gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
-
---Apple-Mail-13-196941151--
-
diff --git a/actionmailer/test/fixtures/raw_email13 b/actionmailer/test/fixtures/raw_email13
deleted file mode 100644
index 7d9314e36a..0000000000
--- a/actionmailer/test/fixtures/raw_email13
+++ /dev/null
@@ -1,29 +0,0 @@
-Mime-Version: 1.0 (Apple Message framework v730)
-Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
-Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
-From: foo@example.com
-Subject: testing
-Date: Mon, 6 Jun 2005 22:21:22 +0200
-To: blah@example.com
-
-
---Apple-Mail-13-196941151
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/plain;
- charset=ISO-8859-1;
- delsp=yes;
- format=flowed
-
-This is the first part.
-
---Apple-Mail-13-196941151
-Content-Type: text/x-ruby-script; name="hello.rb"
-Content-Transfer-Encoding: 7bit
-Content-Disposition: attachment;
- filename="api.rb"
-
-puts "Hello, world!"
-gets
-
---Apple-Mail-13-196941151--
-
diff --git a/actionmailer/test/fixtures/raw_email2 b/actionmailer/test/fixtures/raw_email2
deleted file mode 100644
index 9f87bb2a98..0000000000
--- a/actionmailer/test/fixtures/raw_email2
+++ /dev/null
@@ -1,114 +0,0 @@
-From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
-Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
-X-Original-To: xxxxx@xxxxx.xxxxxxxxx.com
-Delivered-To: xxxxx@xxxxx.xxxxxxxxx.com
-Received: from localhost (localhost [127.0.0.1])
- by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 06C9DA98D
- for <xxxxx@xxxxx.xxxxxxxxx.com>; Sun, 8 May 2005 19:09:13 +0000 (GMT)
-Received: from xxxxx.xxxxxxxxx.com ([127.0.0.1])
- by localhost (xxxxx.xxxxxxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
- with LMTP id 88783-08 for <xxxxx@xxxxx.xxxxxxxxx.com>;
- Sun, 8 May 2005 19:09:12 +0000 (GMT)
-Received: from xxxxxxx.xxxxxxxxx.com (xxxxxxx.xxxxxxxxx.com [69.36.39.150])
- by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 10D8BA960
- for <xxxxx@xxxxxxxxx.org>; Sun, 8 May 2005 19:09:12 +0000 (GMT)
-Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.199])
- by xxxxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 9EBC4148EAB
- for <xxxxx@xxxxxxxxx.com>; Sun, 8 May 2005 14:09:11 -0500 (CDT)
-Received: by zproxy.gmail.com with SMTP id 13so1233405nzp
- for <xxxxx@xxxxxxxxx.com>; Sun, 08 May 2005 12:09:11 -0700 (PDT)
-DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
- s=beta; d=gmail.com;
- h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:references;
- b=cid1mzGEFa3gtRa06oSrrEYfKca2CTKu9sLMkWxjbvCsWMtp9RGEILjUz0L5RySdH5iO661LyNUoHRFQIa57bylAbXM3g2DTEIIKmuASDG3x3rIQ4sHAKpNxP7Pul+mgTaOKBv+spcH7af++QEJ36gHFXD2O/kx9RePs3JNf/K8=
-Received: by 10.36.10.16 with SMTP id 16mr1012493nzj;
- Sun, 08 May 2005 12:09:11 -0700 (PDT)
-Received: by 10.36.5.10 with HTTP; Sun, 8 May 2005 12:09:11 -0700 (PDT)
-Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
-Date: Sun, 8 May 2005 14:09:11 -0500
-From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
-Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
-To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
-Subject: Fwd: Signed email causes file attachments
-In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
-Mime-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="----=_Part_5028_7368284.1115579351471"
-References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
-
-------=_Part_5028_7368284.1115579351471
-Content-Type: text/plain; charset=ISO-8859-1
-Content-Transfer-Encoding: quoted-printable
-Content-Disposition: inline
-
-We should not include these files or vcards as attachments.
-
----------- Forwarded message ----------
-From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
-Date: May 8, 2005 1:17 PM
-Subject: Signed email causes file attachments
-To: xxxxxxx@xxxxxxxxxx.com
-
-
-Hi,
-
-Just started to use my xxxxxxxx account (to set-up a GTD system,
-natch) and noticed that when I send content via email the signature/
-certificate from my email account gets added as a file (e.g.
-"smime.p7s").
-
-Obviously I can uncheck the signature option in the Mail compose
-window but how often will I remember to do that?
-
-Is there any way these kind of files could be ignored, e.g. via some
-sort of exclusions list?
-
-------=_Part_5028_7368284.1115579351471
-Content-Type: application/pkcs7-signature; name=smime.p7s
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment; filename="smime.p7s"
-
-MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
-ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
-d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
-YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
-ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
-7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
-o8xS3A0a1LXealcmlEbJibmKkEaoXci3MhryLgpaa+Kk/sH02SNatDO1vS28bPsibZpcc6deFrla
-hSYnL+PW54mDTGHIcCN2fbx/Y6qspzqmtKaXrv75NBtuy9cB6KzU4j2xXbTkAwz3pRSghJJaAwdp
-+yIivAD3vr0kJE3p+Ez34HMh33EXEpFoWcN+MCEQZD9WnmFViMrvfvMXLGVFQfAAcC060eGFSRJ1
-ZQ9UVQIDAQABoy0wKzAbBgNVHREEFDASgRBzbWhhdW5jaEBtYWMuY29tMAwGA1UdEwEB/wQCMAAw
-DQYJKoZIhvcNAQEEBQADgYEAQMrg1n2pXVWteP7BBj+Pk3UfYtbuHb42uHcLJjfjnRlH7AxnSwrd
-L3HED205w3Cq8T7tzVxIjRRLO/ljq0GedSCFBky7eYo1PrXhztGHCTSBhsiWdiyLWxKlOxGAwJc/
-lMMnwqLOdrQcoF/YgbjeaUFOQbUh94w9VDNpWZYCZwcwggM/MIICqKADAgECAgENMA0GCSqGSIb3
-DQEBBQUAMIHRMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlD
-YXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0
-aW9uIFNlcnZpY2VzIERpdmlzaW9uMSQwIgYDVQQDExtUaGF3dGUgUGVyc29uYWwgRnJlZW1haWwg
-Q0ExKzApBgkqhkiG9w0BCQEWHHBlcnNvbmFsLWZyZWVtYWlsQHRoYXd0ZS5jb20wHhcNMDMwNzE3
-MDAwMDAwWhcNMTMwNzE2MjM1OTU5WjBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENv
-bnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElz
-c3VpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMSmPFVzVftOucqZWh5owHUEcJ3f
-6f+jHuy9zfVb8hp2vX8MOmHyv1HOAdTlUAow1wJjWiyJFXCO3cnwK4Vaqj9xVsuvPAsH5/EfkTYk
-KhPPK9Xzgnc9A74r/rsYPge/QIACZNenprufZdHFKlSFD0gEf6e20TxhBEAeZBlyYLf7AgMBAAGj
-gZQwgZEwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnRo
-YXd0ZS5jb20vVGhhd3RlUGVyc29uYWxGcmVlbWFpbENBLmNybDALBgNVHQ8EBAMCAQYwKQYDVR0R
-BCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGVMYWJlbDItMTM4MA0GCSqGSIb3DQEBBQUAA4GBAEiM
-0VCD6gsuzA2jZqxnD3+vrL7CF6FDlpSdf0whuPg2H6otnzYvwPQcUCCTcDz9reFhYsPZOhl+hLGZ
-GwDFGguCdJ4lUJRix9sncVcljd2pnDmOjCBPZV+V2vf3h9bGCE6u9uo05RAaWzVNd+NWIXiC3CEZ
-Nd4ksdMdRv9dX2VPMYIC5zCCAuMCAQEwaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3Rl
-IENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWls
-IElzc3VpbmcgQ0ECAw5c+TAJBgUrDgMCGgUAoIIBUzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB
-MBwGCSqGSIb3DQEJBTEPFw0wNTA1MDgxODE3NDZaMCMGCSqGSIb3DQEJBDEWBBQSkG9j6+hB0pKp
-fV9tCi/iP59sNTB4BgkrBgEEAYI3EAQxazBpMGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3
-dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h
-aWwgSXNzdWluZyBDQQIDDlz5MHoGCyqGSIb3DQEJEAILMWugaTBiMQswCQYDVQQGEwJaQTElMCMG
-A1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNv
-bmFsIEZyZWVtYWlsIElzc3VpbmcgQ0ECAw5c+TANBgkqhkiG9w0BAQEFAASCAQAm1GeF7dWfMvrW
-8yMPjkhE+R8D1DsiCoWSCp+5gAQm7lcK7V3KrZh5howfpI3TmCZUbbaMxOH+7aKRKpFemxoBY5Q8
-rnCkbpg/++/+MI01T69hF/rgMmrGcrv2fIYy8EaARLG0xUVFSZHSP+NQSYz0TTmh4cAESHMzY3JA
-nHOoUkuPyl8RXrimY1zn0lceMXlweZRouiPGuPNl1hQKw8P+GhOC5oLlM71UtStnrlk3P9gqX5v7
-Tj7Hx057oVfY8FMevjxGwU3EK5TczHezHbWWgTyum9l2ZQbUQsDJxSniD3BM46C1VcbDLPaotAZ0
-fTYLZizQfm5hcWEbfYVzkSzLAAAAAAAA
-------=_Part_5028_7368284.1115579351471--
-
diff --git a/actionmailer/test/fixtures/raw_email3 b/actionmailer/test/fixtures/raw_email3
deleted file mode 100644
index 3a0927490a..0000000000
--- a/actionmailer/test/fixtures/raw_email3
+++ /dev/null
@@ -1,70 +0,0 @@
-From xxxx@xxxx.com Tue May 10 11:28:07 2005
-Return-Path: <xxxx@xxxx.com>
-X-Original-To: xxxx@xxxx.com
-Delivered-To: xxxx@xxxx.com
-Received: from localhost (localhost [127.0.0.1])
- by xxx.xxxxx.com (Postfix) with ESMTP id 50FD3A96F
- for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:50 +0000 (GMT)
-Received: from xxx.xxxxx.com ([127.0.0.1])
- by localhost (xxx.xxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
- with LMTP id 70060-03 for <xxxx@xxxx.com>;
- Tue, 10 May 2005 17:26:49 +0000 (GMT)
-Received: from xxx.xxxxx.com (xxx.xxxxx.com [69.36.39.150])
- by xxx.xxxxx.com (Postfix) with ESMTP id 8B957A94B
- for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:48 +0000 (GMT)
-Received: from xxx.xxxxx.com (xxx.xxxxx.com [64.233.184.203])
- by xxx.xxxxx.com (Postfix) with ESMTP id 9972514824C
- for <xxxx@xxxx.com>; Tue, 10 May 2005 12:26:40 -0500 (CDT)
-Received: by xxx.xxxxx.com with SMTP id 68so1694448wri
- for <xxxx@xxxx.com>; Tue, 10 May 2005 10:26:40 -0700 (PDT)
-DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
- s=beta; d=xxxxx.com;
- h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type;
- b=g8ZO5ttS6GPEMAz9WxrRk9+9IXBUfQIYsZLL6T88+ECbsXqGIgfGtzJJFn6o9CE3/HMrrIGkN5AisxVFTGXWxWci5YA/7PTVWwPOhJff5BRYQDVNgRKqMl/SMttNrrRElsGJjnD1UyQ/5kQmcBxq2PuZI5Zc47u6CILcuoBcM+A=
-Received: by 10.54.96.19 with SMTP id t19mr621017wrb;
- Tue, 10 May 2005 10:26:39 -0700 (PDT)
-Received: by 10.54.110.5 with HTTP; Tue, 10 May 2005 10:26:39 -0700 (PDT)
-Message-ID: <xxxx@xxxx.com>
-Date: Tue, 10 May 2005 11:26:39 -0600
-From: Test Tester <xxxx@xxxx.com>
-Reply-To: Test Tester <xxxx@xxxx.com>
-To: xxxx@xxxx.com, xxxx@xxxx.com
-Subject: Another PDF
-Mime-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="----=_Part_2192_32400445.1115745999735"
-X-Virus-Scanned: amavisd-new at textdrive.com
-
-------=_Part_2192_32400445.1115745999735
-Content-Type: text/plain; charset=ISO-8859-1
-Content-Transfer-Encoding: quoted-printable
-Content-Disposition: inline
-
-Just attaching another PDF, here, to see what the message looks like,
-and to see if I can figure out what is going wrong here.
-
-------=_Part_2192_32400445.1115745999735
-Content-Type: application/pdf; name="broken.pdf"
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment; filename="broken.pdf"
-
-JVBERi0xLjQNCiXk9tzfDQoxIDAgb2JqDQo8PCAvTGVuZ3RoIDIgMCBSDQogICAvRmlsdGVyIC9G
-bGF0ZURlY29kZQ0KPj4NCnN0cmVhbQ0KeJy9Wt2KJbkNvm/od6jrhZxYln9hWEh2p+8HBvICySaE
-ycLuTV4/1ifJ9qnq09NpSBimu76yLUuy/qzqcPz7+em3Ixx/CDc6CsXxs3b5+fvfjr/8cPz6/BRu
-rbfAx/n3739/fuJylJ5u5fjX81OuDr4deK4Bz3z/aDP+8fz0yw8g0Ofq7ktr1Mn+u28rvhy/jVeD
-QSa+9YNKHP/pxjvDNfVAx/m3MFz54FhvTbaseaxiDoN2LeMVMw+yA7RbHSCDzxZuaYB2E1Yay7QU
-x89vz0+tyFDKMlAHK5yqLmnjF+c4RjEiQIUeKwblXMe+AsZjN1J5yGQL5DHpDHksurM81rF6PKab
-gK6zAarIDzIiUY23rJsN9iorAE816aIu6lsgAdQFsuhhkHOUFgVjp2GjMqSewITXNQ27jrMeamkg
-1rPI3iLWG2CIaSBB+V1245YVRICGbbpYKHc2USFDl6M09acQVQYhlwIrkBNLISvXhGlF1wi5FHCw
-wxZkoGNJlVeJCEsqKA+3YAV5AMb6KkeaqEJQmFKKQU8T1pRi2ihE1Y4CDrqoYFFXYjJJOatsyzuI
-8SIlykuxKTMibWK8H1PgEvqYgs4GmQSrEjJAalgGirIhik+p4ZQN9E3ETFPAHE1b8pp1l/0Rc1gl
-fQs0ABWvyoZZzU8VnPXwVVcO9BEsyjEJaO6eBoZRyKGlrKoYoOygA8BGIzgwN3RQ15ouigG5idZQ
-fx2U4Db2CqiLO0WHAZoylGiCAqhniNQjFjQPSkmjwfNTgQ6M1Ih+eWo36wFmjIxDJZiGUBiWsAyR
-xX3EekGOizkGI96Ol9zVZTAivikURhRsHh2E3JhWMpSTZCnnonrLhMCodgrNcgo4uyJUJc6qnVss
-nrGd1Ptr0YwisCOYyIbUwVjV4xBUNLbguSO2YHujonAMJkMdSI7bIw91Akq2AUlMUWGFTMAOamjU
-OvZQCxIkY2pCpMFo/IwLdVLHs6nddwTRrgoVbvLU9eB0G4EMndV0TNoxHbt3JBWwK6hhv3iHfDtF
-yokB302IpEBTnWICde4uYc/1khDbSIkQopO6lcqamGBu1OSE3N5IPSsZX00CkSHRiiyx6HQIShsS
-HSVNswdVsaOUSAWq9aYhDtGDaoG5a3lBGkYt/lFlBFt1UqrYnzVtUpUQnLiZeouKgf1KhRBViRRk
-ExepJCzTwEmFDalIRbLEGtw0gfpESOpIAF/NnpPzcVCG86s0g2DuSyd41uhNGbEgaSrWEXORErbw
-------=_Part_2192_32400445.1115745999735--
-
diff --git a/actionmailer/test/fixtures/raw_email4 b/actionmailer/test/fixtures/raw_email4
deleted file mode 100644
index 639ad40e49..0000000000
--- a/actionmailer/test/fixtures/raw_email4
+++ /dev/null
@@ -1,59 +0,0 @@
-Return-Path: <xxx@xxxx.xxx>
-Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id 6AAEE3B4D23 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:23 -0500
-Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id j48HUC213279 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:13 -0500
-Received: from conversion-xxx.xxxx.xxx.net by xxx.xxxx.xxx id <0IG600901LQ64I@xxx.xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
-Received: from agw1 by xxx.xxxx.xxx with ESMTP id <0IG600JFYLYCAxxx@xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
-Date: Sun, 8 May 2005 12:30:08 -0500
-From: xxx@xxxx.xxx
-To: xxx@xxxx.xxx
-Message-Id: <7864245.1115573412626.JavaMxxx@xxxx.xxx>
-Subject: Filth
-Mime-Version: 1.0
-Content-Type: multipart/mixed; boundary=mimepart_427e4cb4ca329_133ae40413c81ef
-X-Mms-Priority: 1
-X-Mms-Transaction-Id: 3198421808-0
-X-Mms-Message-Type: 0
-X-Mms-Sender-Visibility: 1
-X-Mms-Read-Reply: 1
-X-Original-To: xxx@xxxx.xxx
-X-Mms-Message-Class: 0
-X-Mms-Delivery-Report: 0
-X-Mms-Mms-Version: 16
-Delivered-To: xxx@xxxx.xxx
-X-Nokia-Ag-Version: 2.0
-
-This is a multi-part message in MIME format.
-
---mimepart_427e4cb4ca329_133ae40413c81ef
-Content-Type: multipart/mixed; boundary=mimepart_427e4cb4cbd97_133ae40413c8217
-
-
-
---mimepart_427e4cb4cbd97_133ae40413c8217
-Content-Type: text/plain; charset=utf-8
-Content-Transfer-Encoding: 7bit
-Content-Disposition: inline
-Content-Location: text.txt
-
-Some text
-
---mimepart_427e4cb4cbd97_133ae40413c8217--
-
---mimepart_427e4cb4ca329_133ae40413c81ef
-Content-Type: text/plain; charset=us-ascii
-Content-Transfer-Encoding: 7bit
-
-
---
-This Orange Multi Media Message was sent wirefree from an Orange
-MMS phone. If you would like to reply, please text or phone the
-sender directly by using the phone number listed in the sender's
-address. To learn more about Orange's Multi Media Messaging
-Service, find us on the Web at xxx.xxxx.xxx.uk/mms
-
-
---mimepart_427e4cb4ca329_133ae40413c81ef
-
-
---mimepart_427e4cb4ca329_133ae40413c81ef-
-
diff --git a/actionmailer/test/fixtures/raw_email5 b/actionmailer/test/fixtures/raw_email5
deleted file mode 100644
index bbe31bcdc5..0000000000
--- a/actionmailer/test/fixtures/raw_email5
+++ /dev/null
@@ -1,19 +0,0 @@
-Return-Path: <xxx@xxxx.xxx>
-Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
-Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
-Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
-Date: Tue, 10 May 2005 15:27:03 -0500
-From: xxx@xxxx.xxx
-Sender: xxx@xxxx.xxx
-To: xxxxxxxxxxx@xxxx.xxxx.xxx
-Message-Id: <xxx@xxxx.xxx>
-X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
-Delivered-To: xxx@xxxx.xxx
-Importance: normal
-
-Test test. Hi. Waving. m
-
-----------------------------------------------------------------
-Sent via Bell Mobility's Text Messaging service.
-Envoyé par le service de messagerie texte de Bell Mobilité.
-----------------------------------------------------------------
diff --git a/actionmailer/test/fixtures/raw_email6 b/actionmailer/test/fixtures/raw_email6
deleted file mode 100644
index 8e37bd7392..0000000000
--- a/actionmailer/test/fixtures/raw_email6
+++ /dev/null
@@ -1,20 +0,0 @@
-Return-Path: <xxx@xxxx.xxx>
-Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
-Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
-Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
-Date: Tue, 10 May 2005 15:27:03 -0500
-From: xxx@xxxx.xxx
-Sender: xxx@xxxx.xxx
-To: xxxxxxxxxxx@xxxx.xxxx.xxx
-Message-Id: <xxx@xxxx.xxx>
-X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
-Delivered-To: xxx@xxxx.xxx
-Importance: normal
-Content-Type: text/plain; charset=us-ascii
-
-Test test. Hi. Waving. m
-
-----------------------------------------------------------------
-Sent via Bell Mobility's Text Messaging service.
-Envoyé par le service de messagerie texte de Bell Mobilité.
-----------------------------------------------------------------
diff --git a/actionmailer/test/fixtures/raw_email7 b/actionmailer/test/fixtures/raw_email7
deleted file mode 100644
index da64ada8a5..0000000000
--- a/actionmailer/test/fixtures/raw_email7
+++ /dev/null
@@ -1,66 +0,0 @@
-Mime-Version: 1.0 (Apple Message framework v730)
-Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
-Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
-From: foo@example.com
-Subject: testing
-Date: Mon, 6 Jun 2005 22:21:22 +0200
-To: blah@example.com
-
-
---Apple-Mail-13-196941151
-Content-Type: multipart/mixed;
- boundary=Apple-Mail-12-196940926
-
-
---Apple-Mail-12-196940926
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/plain;
- charset=ISO-8859-1;
- delsp=yes;
- format=flowed
-
-This is the first part.
-
---Apple-Mail-12-196940926
-Content-Transfer-Encoding: 7bit
-Content-Type: text/x-ruby-script;
- x-unix-mode=0666;
- name="test.rb"
-Content-Disposition: attachment;
- filename=test.rb
-
-puts "testing, testing"
-
---Apple-Mail-12-196940926
-Content-Transfer-Encoding: base64
-Content-Type: application/pdf;
- x-unix-mode=0666;
- name="test.pdf"
-Content-Disposition: inline;
- filename=test.pdf
-
-YmxhaCBibGFoIGJsYWg=
-
---Apple-Mail-12-196940926
-Content-Transfer-Encoding: 7bit
-Content-Type: text/plain;
- charset=US-ASCII;
- format=flowed
-
-
-
---Apple-Mail-12-196940926--
-
---Apple-Mail-13-196941151
-Content-Transfer-Encoding: base64
-Content-Type: application/pkcs7-signature;
- name=smime.p7s
-Content-Disposition: attachment;
- filename=smime.p7s
-
-jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
-ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
-QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
-gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
-
---Apple-Mail-13-196941151--
diff --git a/actionmailer/test/fixtures/raw_email8 b/actionmailer/test/fixtures/raw_email8
deleted file mode 100644
index 79996365b3..0000000000
--- a/actionmailer/test/fixtures/raw_email8
+++ /dev/null
@@ -1,47 +0,0 @@
-From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
-Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
-Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
-Date: Sun, 8 May 2005 14:09:11 -0500
-From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
-Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
-To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
-Subject: Fwd: Signed email causes file attachments
-In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
-Mime-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="----=_Part_5028_7368284.1115579351471"
-References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
-
-------=_Part_5028_7368284.1115579351471
-Content-Type: text/plain; charset=ISO-8859-1
-Content-Transfer-Encoding: quoted-printable
-Content-Disposition: inline
-
-We should not include these files or vcards as attachments.
-
----------- Forwarded message ----------
-From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
-Date: May 8, 2005 1:17 PM
-Subject: Signed email causes file attachments
-To: xxxxxxx@xxxxxxxxxx.com
-
-
-Hi,
-
-Test attachments oddly encoded with japanese charset.
-
-
-------=_Part_5028_7368284.1115579351471
-Content-Type: application/octet-stream; name*=iso-2022-jp'ja'01%20Quien%20Te%20Dij%8aat.%20Pitbull.mp3
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment
-
-MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
-ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
-d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
-YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
-ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
-7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
-------=_Part_5028_7368284.1115579351471--
-
diff --git a/actionmailer/test/fixtures/raw_email9 b/actionmailer/test/fixtures/raw_email9
deleted file mode 100644
index 02ea0b05c5..0000000000
--- a/actionmailer/test/fixtures/raw_email9
+++ /dev/null
@@ -1,28 +0,0 @@
-Received: from xxx.xxx.xxx ([xxx.xxx.xxx.xxx] verified)
- by xxx.com (CommuniGate Pro SMTP 4.2.8)
- with SMTP id 2532598 for xxx@xxx.com; Wed, 23 Feb 2005 17:51:49 -0500
-Received-SPF: softfail
- receiver=xxx.com; client-ip=xxx.xxx.xxx.xxx; envelope-from=xxx@xxx.xxx
-quite Delivered-To: xxx@xxx.xxx
-Received: by xxx.xxx.xxx (Wostfix, from userid xxx)
- id 0F87F333; Wed, 23 Feb 2005 16:16:17 -0600
-Date: Wed, 23 Feb 2005 18:20:17 -0400
-From: "xxx xxx" <xxx@xxx.xxx>
-Message-ID: <4D6AA7EB.6490534@xxx.xxx>
-To: xxx@xxx.com
-Subject: Stop adware/spyware once and for all.
-X-Scanned-By: MIMEDefang 2.11 (www dot roaringpenguin dot com slash mimedefang)
-
-You are infected with:
-Ad Ware and Spy Ware
-
-Get your free scan and removal download now,
-before it gets any worse.
-
-http://xxx.xxx.info?aid=3D13&?stat=3D4327kdzt
-
-
-
-
-no more? (you will still be infected)
-http://xxx.xxx.info/discon/?xxx@xxx.com
diff --git a/actionmailer/test/fixtures/raw_email_quoted_with_0d0a b/actionmailer/test/fixtures/raw_email_quoted_with_0d0a
deleted file mode 100644
index 8a2c25a5dd..0000000000
--- a/actionmailer/test/fixtures/raw_email_quoted_with_0d0a
+++ /dev/null
@@ -1,14 +0,0 @@
-Mime-Version: 1.0 (Apple Message framework v730)
-Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
-From: foo@example.com
-Subject: testing
-Date: Mon, 6 Jun 2005 22:21:22 +0200
-To: blah@example.com
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/plain
-
-A fax has arrived from remote ID ''.=0D=0A-----------------------=
--------------------------------------=0D=0ATime: 3/9/2006 3:50:52=
- PM=0D=0AReceived from remote ID: =0D=0AInbound user ID XXXXXXXXXX, r=
-outing code XXXXXXXXX=0D=0AResult: (0/352;0/0) Successful Send=0D=0AP=
-age record: 1 - 1=0D=0AElapsed time: 00:58 on channel 11=0D=0A
diff --git a/actionmailer/test/fixtures/raw_email_with_invalid_characters_in_content_type b/actionmailer/test/fixtures/raw_email_with_invalid_characters_in_content_type
deleted file mode 100644
index a8ff7ed4cb..0000000000
--- a/actionmailer/test/fixtures/raw_email_with_invalid_characters_in_content_type
+++ /dev/null
@@ -1,104 +0,0 @@
-Return-Path: <mikel.other@baci>
-Received: from some.isp.com by baci with ESMTP id 632BD5758 for <mikel.lindsaar@baci>; Sun, 21 Oct 2007 19:38:21 +1000
-Date: Sun, 21 Oct 2007 19:38:13 +1000
-From: Mikel Lindsaar <mikel.other@baci>
-Reply-To: Mikel Lindsaar <mikel.other@baci>
-To: mikel.lindsaar@baci
-Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a>
-Subject: Testing outlook
-Mime-Version: 1.0
-Content-Type: multipart/alternative; boundary=----=_NextPart_000_0093_01C81419.EB75E850
-Delivered-To: mikel.lindsaar@baci
-X-Mimeole: Produced By Microsoft MimeOLE V6.00.2900.3138
-X-Msmail-Priority: Normal
-
-This is a multi-part message in MIME format.
-
-
-------=_NextPart_000_0093_01C81419.EB75E850
-Content-Type: text/plain; charset=iso-8859-1
-Content-Transfer-Encoding: Quoted-printable
-
-Hello
-This is an outlook test
-
-So there.
-
-Me.
-
-------=_NextPart_000_0093_01C81419.EB75E850
-Content-Type: text/html; charset=iso-8859-1
-Content-Transfer-Encoding: Quoted-printable
-
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD>
-<META http-equiv=3DContent-Type content=3D"text/html; =
-charset=3Diso-8859-1">
-<META content=3D"MSHTML 6.00.6000.16525" name=3DGENERATOR>
-<STYLE></STYLE>
-</HEAD>
-<BODY bgColor=3D#ffffff>
-<DIV><FONT face=3DArial size=3D2>Hello</FONT></DIV>
-<DIV><FONT face=3DArial size=3D2><STRONG>This is an outlook=20
-test</STRONG></FONT></DIV>
-<DIV><FONT face=3DArial size=3D2><STRONG></STRONG></FONT>&nbsp;</DIV>
-<DIV><FONT face=3DArial size=3D2><STRONG>So there.</STRONG></FONT></DIV>
-<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
-<DIV><FONT face=3DArial size=3D2>Me.</FONT></DIV></BODY></HTML>
-
-
-------=_NextPart_000_0093_01C81419.EB75E850--
-
-
-Return-Path: <mikel.other@baci>
-Received: from some.isp.com by baci with ESMTP id 632BD5758 for <mikel.lindsaar@baci>; Sun, 21 Oct 2007 19:38:21 +1000
-Date: Sun, 21 Oct 2007 19:38:13 +1000
-From: Mikel Lindsaar <mikel.other@baci>
-Reply-To: Mikel Lindsaar <mikel.other@baci>
-To: mikel.lindsaar@baci
-Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a>
-Subject: Testing outlook
-Mime-Version: 1.0
-Content-Type: multipart/alternative; boundary=----=_NextPart_000_0093_01C81419.EB75E850
-Delivered-To: mikel.lindsaar@baci
-X-Mimeole: Produced By Microsoft MimeOLE V6.00.2900.3138
-X-Msmail-Priority: Normal
-
-This is a multi-part message in MIME format.
-
-
-------=_NextPart_000_0093_01C81419.EB75E850
-Content-Type: text/plain; charset=iso-8859-1
-Content-Transfer-Encoding: Quoted-printable
-
-Hello
-This is an outlook test
-
-So there.
-
-Me.
-
-------=_NextPart_000_0093_01C81419.EB75E850
-Content-Type: text/html; charset=iso-8859-1
-Content-Transfer-Encoding: Quoted-printable
-
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<HTML><HEAD>
-<META http-equiv=3DContent-Type content=3D"text/html; =
-charset=3Diso-8859-1">
-<META content=3D"MSHTML 6.00.6000.16525" name=3DGENERATOR>
-<STYLE></STYLE>
-</HEAD>
-<BODY bgColor=3D#ffffff>
-<DIV><FONT face=3DArial size=3D2>Hello</FONT></DIV>
-<DIV><FONT face=3DArial size=3D2><STRONG>This is an outlook=20
-test</STRONG></FONT></DIV>
-<DIV><FONT face=3DArial size=3D2><STRONG></STRONG></FONT>&nbsp;</DIV>
-<DIV><FONT face=3DArial size=3D2><STRONG>So there.</STRONG></FONT></DIV>
-<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
-<DIV><FONT face=3DArial size=3D2>Me.</FONT></DIV></BODY></HTML>
-
-
-------=_NextPart_000_0093_01C81419.EB75E850--
-
-
diff --git a/actionmailer/test/fixtures/raw_email_with_nested_attachment b/actionmailer/test/fixtures/raw_email_with_nested_attachment
deleted file mode 100644
index 429c408c5d..0000000000
--- a/actionmailer/test/fixtures/raw_email_with_nested_attachment
+++ /dev/null
@@ -1,100 +0,0 @@
-From jamis@37signals.com Thu Feb 22 11:20:31 2007
-Mime-Version: 1.0 (Apple Message framework v752.3)
-Message-Id: <2CCE0408-10C7-4045-9B16-A1C11C31469B@37signals.com>
-Content-Type: multipart/signed;
- micalg=sha1;
- boundary=Apple-Mail-42-587703407;
- protocol="application/pkcs7-signature"
-To: Jamis Buck <jamis@jamisbuck.org>
-Subject: Testing attachments
-From: Jamis Buck <jamis@37signals.com>
-Date: Thu, 22 Feb 2007 11:20:31 -0700
-
-
---Apple-Mail-42-587703407
-Content-Type: multipart/mixed;
- boundary=Apple-Mail-41-587703287
-
-
---Apple-Mail-41-587703287
-Content-Transfer-Encoding: 7bit
-Content-Type: text/plain;
- charset=US-ASCII;
- format=flowed
-
-Here is a test of an attachment via email.
-
-- Jamis
-
-
---Apple-Mail-41-587703287
-Content-Transfer-Encoding: base64
-Content-Type: image/png;
- x-unix-mode=0644;
- name=byo-ror-cover.png
-Content-Disposition: inline;
- filename=truncated.png
-
-iVBORw0KGgoAAAANSUhEUgAAAKUAAADXCAYAAAB7wZEQAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
-AAALEgAACxIB0t1+/AAAABd0RVh0Q3JlYXRpb24gVGltZQAxLzI1LzIwMDeD9CJVAAAAGHRFWHRT
-b2Z0d2FyZQBBZG9iZSBGaXJld29ya3NPsx9OAAAyBWlUWHRYTUw6Y29tLmFkb2JlLnhtcDw/eHBh
-Y2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1l
-dGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1j
-MDIwIDEuMjU1NzE2LCBUdWUgT2N0IDEwIDIwMDYgMjM6MTY6MzQiPgogICA8cmRmOlJERiB4bWxu
-czpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAg
-ICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4YXA9Imh0
-dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eGFwOkNyZWF0b3JUb29sPkFk
-b2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhhcDpDcmVhdGVE
-YXRlPjIwMDctMDEtMjVUMDU6Mjg6MjFaPC94YXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpN
-b2RpZnlEYXRlPjIwMDctMDEtMjVUMDU6Mjg6MjFaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9y
-ZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAg
-ICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAg
-ICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0
-hhojpmnJMfaYFmSkXWg5PGCmHXVj/c9At0hSK2xGdd8F3muk0VFjb4f5Ue0ksQ8qAcq0delaXhdb
-DjKNnF+3B3t9kObZYmk7AZgWYqO9anpR3wpM9sQ5XslB9a+kWyTtNb0fOmudzGHfPFBQDKesyycm
-DBL7Cw5bXjIEuci+SSOm/LYnXDZu6iuPEj8lYBb+OU8xx1f9m+e5rhJiYKqjo5vHfiZp+VUkW9xc
-Ufd6JHNWc47PkQqb9ie3SLEZB/ZqyAssiqURY+G35iOMZUrHbasHnb80QAPv9FHtAbJIyro7bi5b
-ai2TEAKen5+LJNWrglZjm3UbZvt7KryA2J5b5J1jZF8kL6GzvG1Zqx54Y1y7J7n20wMOt9frG2sW
-uwGP07kNz3732vf6bfvAvLldfS+9fts2euXY37D+R29FGZdlnhzV4TTFmPJduBP2RbNNua4rTqcT
-Qt7Xy1KUB0AHSdP5AZQYvHZg7WD1XvYeMO1A9HhZPqMX5KXbMBrn2efxns/ee21674efxz4Tp/fq
-2HZ648dgYaC1i3Vq1IbNPq3PvDTPezY9FaRISjvnzWqdgcWN8EJgjnNq+Z7ktOm9l2Nfth28EZi4
-bG/we5JwxM+Tql47/D/X6b38I8/RyxvxPJrX6zvQbo3h9jyJx+C0ALX327QETHl5eYlaYCT5rPTb
-+5/rAq26t3lKIxV/p88hq6ptngdgCzoPjJqndiLfc/6y5A14WeDFGNPct4iUsJBV2bYzLEV7m83s
-6Rp63VPhHKC/g/LzaU9qexJRr56043JWinqAtfZqsSm1sjoznthl54dtCqv+uL4nIY+oYWuc3+nH
-kGfn8b0HQpvOYLQAZUDanbJs3jQhITZEgdarZK+cO6ySlL13rut5nFaN23s7u3Snz6eRPTkCoc2/
-Vp1zHfZVFpZ87FiMVLV1iqyK5rlzfji2GzjfDsodlD+Weo5UD4h6PwKqzQMqID0tq2VjjFVSMpis
-ZLRAs7sePZBZAHI+gIanB8I7MD+femAceeUe2Kxa5jS950kZ1p5eNEdeX1+jFmSpZ+1EdWCsDcne
-NPNgUHNw3aYpnzv9PGTX0uo94EtN9qq1rOdxe3kc79T8ukeHJJ8Fnxej6qlylbLLsjQLOy6Xy2a1
-kefs/N+nM7+S7IG5/E5Yc7F003pWErLjbH0O5cGadiMptSB/DZ5U5DI9yeg5MFYyMj8lC/Y7/Xjq
-OZlWcnpg9aQfXz2HRq+Wn5xOp6gN8tWq8R44e2pfyzLYemEgprst+XXk2Zj2nXlbsG05BprndTMv
-C3QRaXczshhVsHnMgfYn80Y2g5JureA6wBasPeP7LkE/jvZMJAaf/g/U2RelHsisvan5FqweIAHg
-Pwc7L68GxvVDAAAAAElFTkSuQmCC
-
---Apple-Mail-41-587703287--
-
---Apple-Mail-42-587703407
-Content-Transfer-Encoding: base64
-Content-Type: application/pkcs7-signature;
- name=smime.p7s
-Content-Disposition: attachment;
- filename=smime.p7s
-
-MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGJzCCAuAw
-ggJJoAMCAQICEFjnFNYXwDEZRWY5EkfzopUwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCWkEx
-JTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5nIChQdHkpIEx0ZC4xLDAqBgNVBAMTI1RoYXd0ZSBQ
-ZXJzb25hbCBGcmVlbWFpbCBJc3N1aW5nIENBMB4XDTA2MDkxMjE3MDExMloXDTA3MDkxMjE3MDEx
-MlowRTEfMB0GA1UEAxMWVGhhd3RlIEZyZWVtYWlsIE1lbWJlcjEiMCAGCSqGSIb3DQEJARYTamFt
-aXNAMzdzaWduYWxzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO2A9JeOFIFJ
-G6z8pTcAldrZ2nMe+Xb1tNrbHgoVzN/QhHXM4qst2Ml93cmFLjMmwG7P9RJeU4oNx+jTqVoBB7NV
-Ne1/o56Do0KhfMZ9iUDQdPLbkZMq4EEpFMdm6PyM3muRKwPhj66iAWe/osCb8DowUK2f66vaRx0Z
-Y0MQHIIrXE02Ta4IfAhIfPqBLkZ4WgTYBHN9vMdYea1jF0GO4gqGk1wqwb3yxv2QMYMbwJ6SI+k/
-ZjkSR/OilTCBhwYLKoZIhvcNAQkQAgsxeKB2MGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3
-dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h
-aWwgSXNzdWluZyBDQQIQWOcU1hfAMRlFZjkSR/OilTANBgkqhkiG9w0BAQEFAASCAQCfwQiC3v6/
-yleRDGv3bJ4nQYQ+c3mz3+mn3Xi6uU35n3piwxZZaWRdmLyiXPvU+QReHpSf3l2qsEZM3sdE0XF9
-eRul/+QTFJcDNXOEAxG1zC2Gpz+6c6RrX4Ou12Pwkp+pNrZWTSY/mZgdqcArupOBcZi7qBjoWcy5
-wb54dfvSSjrjmqLbkH/E8ww/6gGQuU/xXpAUZgUrTmQHrNKeIdSh5oDkOxFaFWvnmb8Z/2ixKqW/
-Ux6WqamyvBtTs/5YBEtnpZOk+uVoscYEUBhU+DVJ2OSvTdXSivMtBdXmGTsG22k+P1NGUHi/A7ev
-xPaO0uk4V8xyjNlN4HPuGpkrlXwPAAAAAAAA
-
---Apple-Mail-42-587703407--
diff --git a/actionmailer/test/fixtures/raw_email_with_partially_quoted_subject b/actionmailer/test/fixtures/raw_email_with_partially_quoted_subject
deleted file mode 100644
index e86108da1e..0000000000
--- a/actionmailer/test/fixtures/raw_email_with_partially_quoted_subject
+++ /dev/null
@@ -1,14 +0,0 @@
-From jamis@37signals.com Mon May 2 16:07:05 2005
-Mime-Version: 1.0 (Apple Message framework v622)
-Content-Transfer-Encoding: base64
-Message-Id: <d3b8cf8e49f04480850c28713a1f473e@37signals.com>
-Content-Type: text/plain;
- charset=EUC-KR;
- format=flowed
-To: jamis@37signals.com
-From: Jamis Buck <jamis@37signals.com>
-Subject: Re: Test: =?UTF-8?B?Iua8ouWtlyI=?= mid =?UTF-8?B?Iua8ouWtlyI=?= tail
-Date: Mon, 2 May 2005 16:07:05 -0600
-
-tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin
-wLogSmFtaXPA1LTPtNku
diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb
index 5f52a1bd69..5f0bee88fd 100644
--- a/actionmailer/test/log_subscriber_test.rb
+++ b/actionmailer/test/log_subscriber_test.rb
@@ -24,10 +24,13 @@ class AMLogSubscriberTest < ActionMailer::TestCase
def test_deliver_is_notified
BaseMailer.welcome.deliver
wait
+
assert_equal(1, @logger.logged(:info).size)
assert_match(/Sent mail to system@test.lindsaar.net/, @logger.logged(:info).first)
- assert_equal(1, @logger.logged(:debug).size)
- assert_match(/Welcome/, @logger.logged(:debug).first)
+
+ assert_equal(2, @logger.logged(:debug).size)
+ assert_match(/BaseMailer#welcome: processed outbound mail in [\d.]+ms/, @logger.logged(:debug).first)
+ assert_match(/Welcome/, @logger.logged(:debug).second)
end
def test_receive_is_notified
@@ -39,4 +42,4 @@ class AMLogSubscriberTest < ActionMailer::TestCase
assert_equal(1, @logger.logged(:debug).size)
assert_match(/Jamis/, @logger.logged(:debug).first)
end
-end \ No newline at end of file
+end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 8b78e0f801..dea80abfcd 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,7 +1,113 @@
-* Introduce `BasicRendering` which is the most basic rendering implementation. It
- allows to `render :text` and `render :nothing` without depending on Action View.
+* Add `session#fetch` method
- *Łukasz Strzałkowski*
+ fetch behaves similarly to [Hash#fetch](http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch),
+ with the exception that the returned value is always saved into the session.
+
+ It returns a value from the hash for the given key.
+ If the key can’t be found, there are several options:
+
+ * With no other arguments, it will raise an KeyError exception.
+ * If a default value is given, then that will be returned.
+ * If the optional code block is specified, then that will be run and its result returned.
+
+ *Damien Mathieu*
+
+* Don't let strong parameters mutate the given hash via `fetch`
+
+ Create a new instance if the given parameter is a `Hash` instead of
+ passing it to the `convert_hashes_to_parameters` method since it is
+ overriding its default value.
+
+ *Brendon Murphy*, *Doug Cole*
+
+* Add `params` option to `button_to` form helper, which renders the given hash
+ as hidden form fields.
+
+ *Andy Waite*
+
+* Make assets helpers work in the controllers like it works in the views.
+
+ Example:
+
+ # config/application.rb
+ config.asset_host = 'http://mycdn.com'
+
+ ActionController::Base.helpers.asset_path('fallback.png')
+ # => http://mycdn.com/assets/fallback.png
+
+ Fixes #10051.
+
+ *Tima Maslyuchenko*
+
+* Respect `SCRIPT_NAME` when using `redirect` with a relative path
+
+ Example:
+
+ # application routes.rb
+ mount BlogEngine => '/blog'
+
+ # engine routes.rb
+ get '/admin' => redirect('admin/dashboard')
+
+ This now redirects to the path `/blog/admin/dashboard`, whereas before it would've
+ generated an invalid url because there would be no slash between the host name and
+ the path. It also allows redirects to work where the application is deployed to a
+ subdirectory of a website.
+
+ Fixes #7977.
+
+ *Andrew White*
+
+* Fixing repond_with working directly on the options hash
+ This fixes an issue where the respond_with worked directly with the given
+ options hash, so that if a user relied on it after calling respond_with,
+ the hash wouldn't be the same.
+
+ Fixes #12029.
+
+ *bluehotdog*
+
+* Fix `ActionDispatch::RemoteIp::GetIp#calculate_ip` to only check for spoofing
+ attacks if both `HTTP_CLIENT_IP` and `HTTP_X_FORWARDED_FOR` are set.
+
+ Fixes #10844.
+
+ *Tamir Duberstein*
+
+* Strong parameters should permit nested number as key.
+
+ Fixes #12293.
+
+ *kennyj*
+
+* Fix regex used to detect URI schemes in `redirect_to` to be consistent with
+ RFC 3986.
+
+ *Derek Prior*
+
+* Fix incorrect `assert_redirected_to` failure message for protocol-relative
+ URLs.
+
+ *Derek Prior*
+
+* Fix an issue where router can't recognize downcased url encoding path.
+
+ Fixes #12269.
+
+ *kennyj*
+
+* Fix custom flash type definition. Misusage of the `_flash_types` class variable
+ caused an error when reloading controllers with custom flash types.
+
+ Fixes #12057.
+
+ *Ricardo de Cillo*
+
+* Do not break params filtering on `nil` values.
+
+ Fixes #12149.
+
+ *Vasiliy Ermolovich*
* Separate Action View completely from Action Pack.
@@ -14,21 +120,21 @@
* Fix an issue where :if and :unless controller action procs were being run
before checking for the correct action in the :only and :unless options.
- Fixes #11799
+ Fixes #11799.
*Nicholas Jakobsen*
* Fix an issue where `assert_dom_equal` and `assert_dom_not_equal` were
ignoring the passed failure message argument.
- Fixes #11751
+ Fixes #11751.
*Ryan McGeary*
* Allow REMOTE_ADDR, HTTP_HOST and HTTP_USER_AGENT to be overridden from
the environment passed into `ActionDispatch::TestRequest.new`.
- Fixes #11590
+ Fixes #11590.
*Andrew White*
@@ -43,7 +149,7 @@
* Skip routes pointing to a redirect or mounted application when generating urls
using an options hash as they aren't relevant and generate incorrect urls.
- Fixes #8018
+ Fixes #8018.
*Andrew White*
@@ -61,7 +167,7 @@
* Fix `ActionDispatch::ParamsParser#parse_formatted_parameters` to rewind body input stream on
parsing json params.
- Fixes #11345
+ Fixes #11345.
*Yuri Bol*, *Paul Nikitochkin*
@@ -94,7 +200,7 @@
was setting `request.formats` with an array containing a `nil` value, which
raised an error when setting the controller formats.
- Fixes #10965
+ Fixes #10965.
*Becker*
@@ -103,7 +209,7 @@
no `:to` present in the options hash so should only affect routes using the
shorthand syntax (i.e. endpoint is inferred from the path).
- Fixes #9856
+ Fixes #9856.
*Yves Senn*, *Andrew White*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index b4315c1f57..8a85bf346a 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -21,10 +21,9 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
- s.add_dependency 'rack', '~> 1.5.2'
- s.add_dependency 'rack-test', '~> 0.6.2'
+ s.add_dependency 'rack', '~> 1.5.2'
+ s.add_dependency 'rack-test', '~> 0.6.2'
- s.add_development_dependency 'actionview', version
- s.add_development_dependency 'activemodel', version
- s.add_development_dependency 'tzinfo', '~> 0.3.37'
+ s.add_development_dependency 'actionview', version
+ s.add_development_dependency 'activemodel', version
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index a84ed17bd4..af5de815bb 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -114,11 +114,6 @@ module AbstractController
end
end
- # Define some internal variables that should not be propagated to the view.
- def self.default_protected_instance_vars
- []
- end
-
abstract!
# Calls the action going through the entire action dispatch stack.
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 41f19fba78..6f6079d3c5 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -13,8 +13,19 @@ module AbstractController
module Rendering
extend ActiveSupport::Concern
- def self.default_protected_instance_vars
- super.concat [:@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config]
+ included do
+ class_attribute :protected_instance_variables
+ self.protected_instance_variables = []
+ end
+
+ # Normalize arguments, options and then delegates render_to_body and
+ # sticks the result in self.response_body.
+ # :api: public
+ def render(*args, &block)
+ options = _normalize_render(*args, &block)
+ self.response_body = render_to_body(options)
+ _process_format(rendered_format)
+ self.response_body
end
# Raw rendering of a template to a string.
@@ -29,32 +40,35 @@ module AbstractController
# overridden in order to still return a string.
# :api: plugin
def render_to_string(*args, &block)
+ options = _normalize_render(*args, &block)
+ render_to_body(options)
end
- # Raw rendering of a template.
- # :api: plugin
- def render_to_body(options = {})
- end
-
- # Normalize arguments, options and then delegates render_to_body and
- # sticks the result in self.response_body.
+ # Performs the actual template rendering.
# :api: public
- def render(*args, &block)
+ def render_to_body(options = {})
end
# Return Content-Type of rendered content
# :api: public
def rendered_format
+ Mime::TEXT
end
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
+ @_action_name @_response_body @_formats @_prefixes @_config
+ @_view_context_class @_view_renderer @_lookup_context
+ )
+
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
# :api: public
def view_assigns
hash = {}
- (instance_variables - self.class.default_protected_instance_vars).each do |name|
- hash[name[1..-1]] = instance_variable_get(name)
- end
+ variables = instance_variables
+ variables -= protected_instance_variables
+ variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
+ variables.each { |name| hash[name[1..-1]] = instance_variable_get(name) }
hash
end
@@ -62,7 +76,11 @@ module AbstractController
# render "foo/bar" to render :file => "foo/bar".
# :api: plugin
def _normalize_args(action=nil, options={})
- options
+ if action.is_a? Hash
+ action
+ else
+ options
+ end
end
# Normalize options.
@@ -76,5 +94,18 @@ module AbstractController
def _process_options(options)
options
end
+
+ # Process the rendered format.
+ # :api: private
+ def _process_format(format)
+ end
+
+ # Normalize args and options.
+ # :api: private
+ def _normalize_render(*args, &block)
+ options = _normalize_args(*args, &block)
+ _normalize_options(options)
+ options
+ end
end
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index d6b1908ccb..417d2efec2 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -13,7 +13,6 @@ module ActionController
autoload :Middleware
autoload_under "metal" do
- autoload :BasicRendering, 'action_controller/metal/rendering'
autoload :Compatibility
autoload :ConditionalGet
autoload :Cookies
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index a00eafc9ed..3b0d094f4f 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -3,7 +3,7 @@ require "action_controller/metal/params_wrapper"
module ActionController
# The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>.
- # Modules needes to be included in particluar order. First wee need to have <tt>AbstractController::Rendering</tt> included,
+ # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included,
# next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that
# <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly
# <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of
@@ -14,7 +14,6 @@ module ActionController
#
metal = Class.new(Metal) do
include AbstractController::Rendering
- include ActionController::BasicRendering
end
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
@@ -74,7 +73,7 @@ module ActionController
# <input type="text" name="post[address]" value="hyacintvej">
#
# A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
- # If the address input had been named "post[address][street]", the params would have included
+ # If the address input had been named <tt>post[address][street]</tt>, the params would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
# == Sessions
@@ -261,12 +260,11 @@ module ActionController
include mod
end
- def self.default_protected_instance_vars
- super.concat [
- :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
- :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
- ]
- end
+ # Define some internal variables that should not be propagated to the view.
+ self.protected_instance_variables = [
+ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
+ ]
ActiveSupport.run_load_hooks(:action_controller, self)
end
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 1d77e331f8..65351284b9 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -37,7 +37,7 @@ module ActionController #:nodoc:
end
helper_method type
- _flash_types << type
+ self._flash_types += [type]
end
end
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index b8afce42c9..a2cb6d1e66 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -48,7 +48,7 @@ module ActionController
# You can pass any of the following options to affect the redirect status and response
# * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently)
# * <tt>flash</tt> - Set a flash message when redirecting
- # * <tt>alert</tt> - Set a alert message when redirecting
+ # * <tt>alert</tt> - Set an alert message when redirecting
# * <tt>notice</tt> - Set a notice message when redirecting
#
# ==== Action Options
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 8237db15ca..424473801d 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -1,7 +1,5 @@
module ActionController
module Head
- extend ActiveSupport::Concern
-
# Return a response that has no content (merely headers). The options
# argument is interpreted to be a hash of header names and values.
# This allows you to easily return a response that consists only of
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index b53ae7f29f..a9c3e438fb 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -73,7 +73,11 @@ module ActionController
# Provides a proxy to access helpers methods from outside the view.
def helpers
- @helper_proxy ||= ActionView::Base.new.extend(_helpers)
+ @helper_proxy ||= begin
+ proxy = ActionView::Base.new
+ proxy.config = config.inheritable_copy
+ proxy.extend(_helpers)
+ end
end
# Overwrite modules_for_helpers to accept :all as argument, which loads
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 834d44f045..a072fce1a1 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -326,6 +326,7 @@ module ActionController #:nodoc:
if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
+ options = options.clone
options[:default_response] = collector.response
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
@@ -364,9 +365,7 @@ module ActionController #:nodoc:
format = collector.negotiate_format(request)
if format
- self.content_type ||= format.to_s
- lookup_context.formats = [format.to_sym]
- lookup_context.rendered_format = lookup_context.formats.first
+ _process_format(format)
collector
else
raise ActionController::UnknownFormat
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index e9031f3fac..ab14a61b97 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -71,6 +71,26 @@ module ActionController
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
end
+ def _compute_redirect_to_location(options) #:nodoc:
+ case options
+ # The scheme name consist of a letter followed by any combination of
+ # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
+ # characters; and is terminated by a colon (":").
+ # See http://tools.ietf.org/html/rfc3986#section-3.1
+ # The protocol relative scheme starts with a double slash "//".
+ when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
+ options
+ when String
+ request.protocol + request.host_with_port + options
+ when :back
+ request.headers["Referer"] or raise RedirectBackError
+ when Proc
+ _compute_redirect_to_location options.call
+ else
+ url_for(options)
+ end.delete("\0\r\n")
+ end
+
private
def _extract_redirect_to_status(options, response_status)
if options.is_a?(Hash) && options.key?(:status)
@@ -81,24 +101,5 @@ module ActionController
302
end
end
-
- def _compute_redirect_to_location(options)
- case options
- # The scheme name consist of a letter followed by any combination of
- # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
- # characters; and is terminated by a colon (":").
- # The protocol relative scheme starts with a double slash "//"
- when %r{\A(\w[\w+.-]*:|//).*}
- options
- when String
- request.protocol + request.host_with_port + options
- when :back
- request.headers["Referer"] or raise RedirectBackError
- when Proc
- _compute_redirect_to_location options.call
- else
- url_for(options)
- end.delete("\0\r\n")
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index abed6e53cc..62a3844b04 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -8,8 +8,7 @@ module ActionController
class MissingRenderer < LoadError
def initialize(format)
- @format = format
- super("No renderer defined for format: #{@format}")
+ super "No renderer defined for format: #{format}"
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 21224b9c3b..90f0ef0b1c 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -1,36 +1,4 @@
module ActionController
- # Basic rendering implements the most minimal rendering layer.
- # It only supports rendering :text and :nothing. Passing any other option will
- # result in `UnsupportedOperationError` exception. For more functionality like
- # different formats, layouts etc. you should use `ActionView` gem.
- module BasicRendering
- extend ActiveSupport::Concern
-
- # Render text or nothing (empty string) to response_body
- # :api: public
- def render(*args, &block)
- super(*args, &block)
- opts = args.first
- if opts.has_key?(:text) && opts[:text].present?
- self.response_body = opts[:text]
- elsif opts.has_key?(:nothing) && opts[:nothing]
- self.response_body = " "
- else
- raise UnsupportedOperationError
- end
- end
-
- def rendered_format
- Mime::TEXT
- end
-
- class UnsupportedOperationError < StandardError
- def initialize
- super "Unsupported render operation. BasicRendering supports only :text and :nothing options. For more, you need to include ActionView."
- end
- end
- end
-
module Rendering
extend ActiveSupport::Concern
@@ -44,27 +12,35 @@ module ActionController
def render(*args) #:nodoc:
raise ::AbstractController::DoubleRenderError if self.response_body
super
- self.content_type ||= rendered_format.to_s
- self.response_body
end
# Overwrite render_to_string because body can now be set to a rack body.
def render_to_string(*)
- if self.response_body = super
+ result = super
+ if result.respond_to?(:each)
string = ""
- self.response_body.each { |r| string << r }
+ result.each { |r| string << r }
string
+ else
+ result
end
- ensure
- self.response_body = nil
end
- def render_to_body(*)
- super || " "
+ def render_to_body(options = {})
+ super || if options[:text].present?
+ options[:text]
+ else
+ " "
+ end
end
private
+ def _process_format(format)
+ super
+ self.content_type ||= format.to_s
+ end
+
# Normalize arguments by catching blocks and setting them on :update.
def _normalize_args(action=nil, options={}, &blk) #:nodoc:
options = super
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 573c739da4..bd64b1f812 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -124,6 +124,9 @@ module ActionController #:nodoc:
@loaded = true
end
+ # no-op
+ def destroy; end
+
def exists?
true
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index ae600b1ebe..fcc76f6225 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -284,7 +284,14 @@ module ActionController
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
def fetch(key, *args)
- convert_hashes_to_parameters(key, super)
+ value = super
+ # Don't rely on +convert_hashes_to_parameters+
+ # so as to not mutate via a +fetch+
+ if value.is_a?(Hash)
+ value = self.class.new(value)
+ value.permit! if permitted?
+ end
+ value
rescue KeyError
raise ActionController::ParameterMissing.new(key)
end
@@ -334,7 +341,7 @@ module ActionController
def each_element(object)
if object.is_a?(Array)
object.map { |el| yield el }.compact
- elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ }
+ elsif fields_for_style?(object)
hash = object.class.new
object.each { |k,v| hash[k] = yield v }
hash
@@ -343,6 +350,10 @@ module ActionController
end
end
+ def fields_for_style?(object)
+ object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ end
+
def unpermitted_parameters!(params)
unpermitted_keys = unpermitted_keys(params)
if unpermitted_keys.any?
@@ -421,7 +432,7 @@ module ActionController
# Slicing filters out non-declared keys.
slice(*filter.keys).each do |key, value|
- return unless value
+ next unless value
if filter[key] == EMPTY_ARRAY
# Declaration { comment_ids: [] }.
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index da0cddd93c..971cb3447f 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -9,8 +9,8 @@ module ActionDispatch
attr_reader :memos
def initialize
- @regexp_states = Hash.new { |h,k| h[k] = {} }
- @string_states = Hash.new { |h,k| h[k] = {} }
+ @regexp_states = {}
+ @string_states = {}
@accepting = {}
@memos = Hash.new { |h,k| h[k] = [] }
end
@@ -111,14 +111,8 @@ module ActionDispatch
end
def []=(from, to, sym)
- case sym
- when String
- @string_states[from][sym] = to
- when Regexp
- @regexp_states[from][sym] = to
- else
- raise ArgumentError, 'unknown symbol: %s' % sym.class
- end
+ to_mappings = states_hash_for(sym)[from] ||= {}
+ to_mappings[sym] = to
end
def states
@@ -137,18 +131,35 @@ module ActionDispatch
private
+ def states_hash_for(sym)
+ case sym
+ when String
+ @string_states
+ when Regexp
+ @regexp_states
+ else
+ raise ArgumentError, 'unknown symbol: %s' % sym.class
+ end
+ end
+
def move_regexp(t, a)
return [] if t.empty?
t.map { |s|
- @regexp_states[s].map { |re, v| re === a ? v : nil }
+ if states = @regexp_states[s]
+ states.map { |re, v| re === a ? v : nil }
+ end
}.flatten.compact.uniq
end
def move_string(t, a)
return [] if t.empty?
- t.map { |s| @string_states[s][a] }.compact
+ t.map do |s|
+ if states = @string_states[s]
+ states[a]
+ end
+ end.compact
end
end
end
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
index 462f1a122d..d1a004af50 100644
--- a/actionpack/lib/action_dispatch/journey/router/utils.rb
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -7,15 +7,18 @@ module ActionDispatch
# Normalizes URI path.
#
# Strips off trailing slash and ensures there is a leading slash.
+ # Also converts downcase url encoded string to uppercase.
#
# normalize_path("/foo") # => "/foo"
# normalize_path("/foo/") # => "/foo"
# normalize_path("foo") # => "/foo"
# normalize_path("") # => "/"
+ # normalize_path("/%ab") # => "/%AB"
def self.normalize_path(path)
path = "/#{path}"
path.squeeze!('/')
path.sub!(%r{/+\Z}, '')
+ path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
path = '/' if path == ''
path
end
@@ -35,7 +38,7 @@ module ActionDispatch
UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
end
- Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
+ Parser = URI::Parser.new
def self.escape_path(path)
Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index 0a8cb1b4d4..9e66cab052 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -1,9 +1,12 @@
# encoding: utf-8
+
+require 'thread_safe'
+
module ActionDispatch
module Journey # :nodoc:
module Visitors # :nodoc:
class Visitor # :nodoc:
- DISPATCH_CACHE = Hash.new { |h,k|
+ DISPATCH_CACHE = ThreadSafe::Cache.new { |h,k|
h[k] = :"visit_#{k}"
}
@@ -84,44 +87,43 @@ module ActionDispatch
# Used for formatting urls (url_for)
class Formatter < Visitor # :nodoc:
- attr_reader :options, :consumed
+ attr_reader :options
def initialize(options)
@options = options
- @consumed = {}
end
private
- def visit_GROUP(node)
- if consumed == options
- nil
- else
- route = visit(node.left)
- route.include?("\0") ? nil : route
+ def visit(node, optional = false)
+ case node.type
+ when :LITERAL, :SLASH, :DOT
+ node.left
+ when :STAR
+ visit(node.left)
+ when :GROUP
+ visit(node.left, true)
+ when :CAT
+ visit_CAT(node, optional)
+ when :SYMBOL
+ visit_SYMBOL(node)
end
end
- def terminal(node)
- node.left
- end
-
- def binary(node)
- [visit(node.left), visit(node.right)].join
- end
+ def visit_CAT(node, optional)
+ left = visit(node.left, optional)
+ right = visit(node.right, optional)
- def nary(node)
- node.children.map { |c| visit(c) }.join
+ if optional && !(right && left)
+ ""
+ else
+ [left, right].join
+ end
end
def visit_SYMBOL(node)
- key = node.to_sym
-
- if value = options[key]
- consumed[key] = value
+ if value = options[node.to_sym]
Router::Utils.escape_path(value)
- else
- "\0"
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 8879291dbd..57bc6d5cd0 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -143,7 +143,7 @@ module ActionDispatch
# proxies with incompatible IP header conventions, and there is no way
# for us to determine which header is the right one after the fact.
# Since we have no idea, we give up and explode.
- should_check_ip = @check_ip && client_ips.last
+ should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
raise IpSpoofAttackError, "IP spoofing attack?! " +
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index fcc5bc12c4..1d4f0f89a6 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -29,8 +29,11 @@ module ActionDispatch
def call(env)
@app.call(env)
rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
+ if env['action_dispatch.show_exceptions'] == false
+ raise exception
+ else
+ render_exception(env, exception)
+ end
end
private
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 7bc812fd22..6d911a75f1 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -127,6 +127,18 @@ module ActionDispatch
@delegate.delete key.to_s
end
+ def fetch(key, default=nil)
+ if self.key?(key)
+ self[key]
+ elsif default
+ self[key] = default
+ elsif block_given?
+ self[key] = yield(key)
+ else
+ raise KeyError
+ end
+ end
+
def inspect
if loaded?
super
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 288ce3e867..db9c993590 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -362,8 +362,9 @@ module ActionDispatch
# # Yes, controller actions are just rack endpoints
# match 'photos/:id', to: PhotosController.action(:show)
#
- # Because request various HTTP verbs with a single action has security
- # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers]
+ # Because requesting various HTTP verbs with a single action has security
+ # implications, you must either specify the actions in
+ # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers]
# instead +match+
#
# === Options
@@ -432,10 +433,10 @@ module ActionDispatch
#
# match 'json_only', constraints: { format: 'json' }
#
- # class Blacklist
+ # class Whitelist
# def matches?(request) request.remote_ip == '1.2.3.4' end
# end
- # match 'path', to: 'c#a', constraints: Blacklist.new
+ # match 'path', to: 'c#a', constraints: Whitelist.new
#
# See <tt>Scoping#constraints</tt> for more examples with its scope
# equivalent.
@@ -1066,18 +1067,18 @@ module ActionDispatch
# a singular resource to map /profile (rather than /profile/:id) to
# the show action:
#
- # resource :geocoder
+ # resource :profile
#
# creates six different routes in your application, all mapping to
- # the +GeoCoders+ controller (note that the controller is named after
+ # the +Profiles+ controller (note that the controller is named after
# the plural):
#
- # GET /geocoder/new
- # POST /geocoder
- # GET /geocoder
- # GET /geocoder/edit
- # PATCH/PUT /geocoder
- # DELETE /geocoder
+ # GET /profile/new
+ # POST /profile
+ # GET /profile
+ # GET /profile/edit
+ # PATCH/PUT /profile
+ # DELETE /profile
#
# === Options
# Takes same options as +resources+.
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 6d3f8da932..2fb03f2712 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -74,6 +74,19 @@ module ActionDispatch
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
+ # Also includes all the options from <tt>url_for</tt>. These include such
+ # things as <tt>:anchor</tt> or <tt>:trailing_slash</tt>. Example usage
+ # is given below:
+ #
+ # polymorphic_url([blog, post], anchor: 'my_anchor')
+ # # => "http://example.com/blogs/1/posts/1#my_anchor"
+ # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app")
+ # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor"
+ #
+ # For all of these options, see the documentation for <tt>url_for</tt>.
+ #
+ # ==== Functionality
+ #
# # an Article record
# polymorphic_url(record) # same as article_url(record)
#
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index d751e04e6a..3e54c7e71c 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -17,7 +17,7 @@ module ActionDispatch
def call(env)
req = Request.new(env)
- # If any of the path parameters has a invalid encoding then
+ # If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
req.symbolized_path_parameters.each do |key, value|
unless value.valid_encoding?
@@ -30,6 +30,10 @@ module ActionDispatch
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?
+ if relative_path?(uri.path)
+ uri.path = "#{req.script_name}/#{uri.path}"
+ end
+
body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
headers = {
@@ -48,6 +52,11 @@ module ActionDispatch
def inspect
"redirect(#{status})"
end
+
+ private
+ def relative_path?(path)
+ path && !path.empty? && path[0] != '/'
+ end
end
class PathRedirect < Redirect
@@ -81,6 +90,11 @@ module ActionDispatch
url_options[:path] = (url_options[:path] % escape_path(params))
end
+ if relative_path?(url_options[:path])
+ url_options[:path] = "/#{url_options[:path]}"
+ url_options[:script_name] = request.script_name
+ end
+
ActionDispatch::Http::URL.url_for url_options
end
@@ -104,6 +118,10 @@ module ActionDispatch
#
# get 'docs/:article', to: redirect('/wiki/%{article}')
#
+ # Note that if you return a path without a leading slash then the url is prefixed with the
+ # current SCRIPT_NAME environment variable. This is typically '/' but may be different in
+ # a mounted engine or where the application is deployed to a subdirectory of a website.
+ #
# Alternatively you can use one of the other syntaxes:
#
# The block version of redirect allows for the easy encapsulation of any logic associated with
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 0e5dc1fc6c..b8abdabca5 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -28,7 +28,7 @@ module ActionDispatch
def call(env)
params = env[PARAMETERS_KEY]
- # If any of the path parameters has a invalid encoding then
+ # If any of the path parameters has an invalid encoding then
# raise since it's likely to trigger errors further on.
params.each do |key, value|
next unless value.respond_to?(:valid_encoding?)
@@ -514,11 +514,12 @@ module ActionDispatch
@recall = recall.dup
@set = set
+ normalize_recall!
normalize_options!
normalize_controller_action_id!
use_relative_controller!
normalize_controller!
- handle_nil_action!
+ normalize_action!
end
def controller
@@ -537,6 +538,11 @@ module ActionDispatch
end
end
+ # Set 'index' as default action for recall
+ def normalize_recall!
+ @recall[:action] ||= 'index'
+ end
+
def normalize_options!
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
@@ -552,8 +558,8 @@ module ActionDispatch
options[:controller] = options[:controller].to_s
end
- if options[:action]
- options[:action] = options[:action].to_s
+ if options.key?(:action)
+ options[:action] = (options[:action] || 'index').to_s
end
end
@@ -563,8 +569,6 @@ module ActionDispatch
# :controller, :action or :id is not found, don't pull any
# more keys from the recall.
def normalize_controller_action_id!
- @recall[:action] ||= 'index' if current_controller
-
use_recall_for(:controller) or return
use_recall_for(:action) or return
use_recall_for(:id)
@@ -586,13 +590,11 @@ module ActionDispatch
@options[:controller] = controller.sub(%r{^/}, '') if controller
end
- # This handles the case of action: nil being explicitly passed.
- # It is identical to action: "index"
- def handle_nil_action!
- if options.has_key?(:action) && options[:action].nil?
- options[:action] = 'index'
+ # Move 'index' action from options to recall
+ def normalize_action!
+ if @options[:action] == 'index'
+ @recall[:action] = @options.delete(:action)
end
- recall[:action] = options.delete(:action) if options[:action] == 'index'
end
# Generates a path from routes, returns [path, params].
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 8e19025722..bcebe532bf 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -20,7 +20,7 @@ module ActionDispatch
#
# <%= link_to('Click here', controller: 'users',
# action: 'new', message: 'Welcome!') %>
- # # => "/users/new?message=Welcome%21"
+ # # => <a href="/users/new?message=Welcome%21">Click here</a>
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlFor under the hood. And in particular,
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 44ed0ac1f3..93f9fab9c2 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -67,21 +67,11 @@ module ActionDispatch
end
def normalize_argument_to_redirection(fragment)
- normalized = case fragment
- when Regexp
- fragment
- when %r{^\w[A-Za-z\d+.-]*:.*}
- fragment
- when String
- @request.protocol + @request.host_with_port + fragment
- when :back
- raise RedirectBackError unless refer = @request.headers["Referer"]
- refer
- else
- @controller.url_for(fragment)
- end
-
- normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
+ if Regexp === fragment
+ fragment
+ else
+ @controller._compute_redirect_to_location(fragment)
+ end
end
end
end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 22a410db94..ba4cffcd3e 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -39,6 +39,8 @@ class ActionPackAssertionsController < ActionController::Base
def redirect_external() redirect_to "http://www.rubyonrails.org"; end
+ def redirect_external_protocol_relative() redirect_to "//www.rubyonrails.org"; end
+
def response404() head '404 AWOL' end
def response500() head '500 Sorry' end
@@ -258,6 +260,19 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
end
end
+ def test_assert_redirect_failure_message_with_protocol_relative_url
+ begin
+ process :redirect_external_protocol_relative
+ assert_redirected_to "/foo"
+ rescue ActiveSupport::TestCase::Assertion => ex
+ assert_no_match(
+ /#{request.protocol}#{request.host}\/\/www.rubyonrails.org/,
+ ex.message,
+ 'protocol relative url was incorrectly normalized'
+ )
+ end
+ end
+
def test_template_objects_exist
process :assign_this
assert !@controller.instance_variable_defined?(:"@hi")
@@ -309,6 +324,9 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
process :redirect_external
assert_equal 'http://www.rubyonrails.org', @response.redirect_url
+
+ process :redirect_external_protocol_relative
+ assert_equal '//www.rubyonrails.org', @response.redirect_url
end
def test_no_redirect_url
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index a67dff5436..57b45b8f7b 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -20,7 +20,7 @@ class FragmentCachingMetalTest < ActionController::TestCase
@controller = FragmentCachingMetalTestController.new
@controller.perform_caching = true
@controller.cache_store = @store
- @params = { controller: 'posts', action: 'index'}
+ @params = { controller: 'posts', action: 'index' }
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@controller.params = @params
@@ -40,7 +40,7 @@ class CachingController < ActionController::Base
end
class FragmentCachingTestController < CachingController
- def some_action; end;
+ def some_action; end
end
class FragmentCachingTest < ActionController::TestCase
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index e58a1aadb8..3b5d7ef446 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -17,7 +17,7 @@ class ActionController::Base
def assigns(key = nil)
assigns = {}
instance_variables.each do |ivar|
- next if ActionController::Base.default_protected_instance_vars.include?(ivar)
+ next if ActionController::Base.protected_instance_variables.include?(ivar)
assigns[ivar[1..-1]] = instance_variable_get(ivar)
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 3b874a739a..c64ffef654 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -214,6 +214,18 @@ class FlashTest < ActionController::TestCase
get :redirect_with_foo_flash
assert_equal "for great justice", @controller.send(:flash)[:foo]
end
+
+ class SubclassesTestController < TestController; end
+
+ def test_add_flash_type_to_subclasses
+ TestController.add_flash_types :foo
+ assert SubclassesTestController._flash_types.include?(:foo)
+ end
+
+ def test_do_not_add_flash_type_to_parent_class
+ SubclassesTestController.add_flash_types :bar
+ assert_not TestController._flash_types.include?(:bar)
+ end
end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 248c81193e..20f99f19ee 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -201,6 +201,12 @@ class HelperTest < ActiveSupport::TestCase
# fun/pdf_helper.rb
assert methods.include?(:foobar)
end
+
+ def test_helper_proxy_config
+ AllHelpersController.config.my_var = 'smth'
+
+ assert_equal 'smth', AllHelpersController.helpers.config.my_var
+ end
private
def expected_helper_methods
diff --git a/actionpack/test/controller/mime/respond_with_test.rb b/actionpack/test/controller/mime/respond_with_test.rb
index 76af9e3414..a70592fa1b 100644
--- a/actionpack/test/controller/mime/respond_with_test.rb
+++ b/actionpack/test/controller/mime/respond_with_test.rb
@@ -65,7 +65,17 @@ class RespondWithController < ActionController::Base
respond_with(resource, :responder => responder)
end
+ def respond_with_additional_params
+ @params = RespondWithController.params
+ respond_with({:result => resource}, @params)
+ end
+
protected
+ def self.params
+ {
+ :foo => 'bar'
+ }
+ end
def resource
Customer.new("david", request.delete? ? nil : 13)
@@ -145,6 +155,11 @@ class RespondWithControllerTest < ActionController::TestCase
Mime::Type.unregister(:mobile)
end
+ def test_respond_with_shouldnt_modify_original_hash
+ get :respond_with_additional_params
+ assert_equal RespondWithController.params, assigns(:params)
+ end
+
def test_using_resource
@request.accept = "application/xml"
get :using_resource
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index f4bdd3e1d4..2b36a399bb 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -92,7 +92,7 @@ module RenderStreaming
io.rewind
assert_match "(undefined method `invalid!' for nil:NilClass)", io.read
ensure
- ActionController::Base.logger = _old
+ ActionView::Base.logger = _old
end
end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index d6c3926a4d..2a253799f3 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -1,6 +1,15 @@
require 'abstract_unit'
module RenderText
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render text: "Hello World!"
+ end
+ end
+
class SimpleController < ActionController::Base
self.view_paths = [ActionView::FixtureResolver.new]
@@ -63,6 +72,12 @@ module RenderText
end
class RenderTextTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_text/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
set.draw { get ':controller', :action => 'index' }
diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb
index 91df527dec..3b1257e8d5 100644
--- a/actionpack/test/controller/parameters/nested_parameters_test.rb
+++ b/actionpack/test/controller/parameters/nested_parameters_test.rb
@@ -169,4 +169,19 @@ class NestedParametersTest < ActiveSupport::TestCase
assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death
end
+
+ test "nested number as key" do
+ params = ActionController::Parameters.new({
+ product: {
+ properties: {
+ '0' => "prop0",
+ '1' => "prop1"
+ }
+ }
+ })
+ params = params.require(:product).permit(:properties => ["0"])
+ assert_not_nil params[:properties]["0"]
+ assert_nil params[:properties]["1"]
+ assert_equal "prop0", params[:properties]["0"]
+ end
end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 437da43d9b..b60c5f058d 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -107,6 +107,15 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal [], permitted[:id]
end
+ test 'do not break params filtering on nil values' do
+ params = ActionController::Parameters.new(a: 1, b: [1, 2, 3], c: nil)
+
+ permitted = params.permit(:a, c: [], b: [])
+ assert_equal 1, permitted[:a]
+ assert_equal [1, 2, 3], permitted[:b]
+ assert_equal nil, permitted[:c]
+ end
+
test 'key to empty array: arrays of permitted scalars pass' do
[['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array|
params = ActionController::Parameters.new(id: array)
@@ -138,6 +147,12 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_equal :foo, e.param
end
+ test "fetch with a default value of a hash does not mutate the object" do
+ params = ActionController::Parameters.new({})
+ params.fetch :foo, {}
+ assert_equal nil, params[:foo]
+ end
+
test "fetch doesnt raise ParameterMissing exception if there is a default" do
assert_equal "monkey", @params.fetch(:foo, "monkey")
assert_equal "monkey", @params.fetch(:foo) { "monkey" }
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index c272e785c2..727db79241 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -78,6 +78,11 @@ class RequestForgeryProtectionControllerUsingNullSession < ActionController::Bas
cookies.encrypted[:foo] = 'bar'
render :nothing => true
end
+
+ def try_to_reset_session
+ reset_session
+ render :nothing => true
+ end
end
class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
@@ -320,6 +325,11 @@ class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController:
post :encrypted
assert_response :ok
end
+
+ test 'should allow reset_session' do
+ post :try_to_reset_session
+ assert_response :ok
+ end
end
class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index f735564305..46df1a7bd5 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1904,6 +1904,10 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界'), :method => :get))
end
+ def test_downcased_unicode_path
+ assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界').downcase, :method => :get))
+ end
+
private
def sort_extras!(extras)
if extras.length == 2
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index 113608ecf4..e519fff51e 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -31,6 +31,14 @@ module TestGenerationPrefix
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_redirect", :to => redirect("foo")
+ get "/relative_option_redirect", :to => redirect(:path => "foo")
+ get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
+
+ get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_option_redirect", :to => redirect(:path => "/foo")
+ get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
end
routes
@@ -182,6 +190,48 @@ module TestGenerationPrefix
assert_equal "engine", last_response.body
end
+ test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_path_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_option_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_custom_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/awesome/blog/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/awesome/blog/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_path_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_option_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_custom_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
# Inside Application
test "[APP] generating engine's route includes prefix" do
get "/generate"
@@ -281,6 +331,14 @@ module TestGenerationPrefix
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
get "/posts/:id", :to => "posts#show", :as => :post
+
+ get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_option_redirect", :to => redirect(:path => "foo")
+ get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
+
+ get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_option_redirect", :to => redirect(:path => "/foo")
+ get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
end
routes
@@ -331,5 +389,47 @@ module TestGenerationPrefix
get "/posts/1"
assert_equal "/posts/1", last_response.body
end
+
+ test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
+ get "/relative_path_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
+ get "/relative_option_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
+ get "/relative_custom_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
+ get "/absolute_path_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
+ get "/absolute_option_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
+
+ test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
+ get "/absolute_custom_redirect"
+ assert_equal 301, last_response.status
+ assert_equal "http://example.org/foo", last_response.headers["Location"]
+ assert_equal %(<html><body>You are being <a href="http://example.org/foo">redirected</a>.</body></html>), last_response.body
+ end
end
end
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 1517f96fdc..a244d1364c 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -61,6 +61,23 @@ module ActionDispatch
assert_equal([], s.values)
end
+ def test_fetch
+ session = Session.create(store, {}, {})
+
+ session['one'] = '1'
+ assert_equal '1', session.fetch(:one)
+
+ assert_equal '2', session.fetch(:two, '2')
+ assert_equal '2', session.fetch(:two)
+
+ assert_equal 'three', session.fetch(:three) {|el| el.to_s }
+ assert_equal 'three', session.fetch(:three)
+
+ assert_raise KeyError do
+ session.fetch(:four)
+ end
+ end
+
private
def store
Class.new {
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index e4c3ddd3f9..3e9e90a950 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1102,6 +1102,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'projects#index', @response.body
end
+ def test_scoped_root_as_name
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ root :to => 'projects#index', :as => 'projects'
+ end
+ end
+
+ assert_equal '/en', projects_path(:locale => 'en')
+ assert_equal '/', projects_path
+ get '/en'
+ assert_equal 'projects#index', @response.body
+ end
+
def test_scope_with_format_option
draw do
get "direct/index", as: :no_format_direct, format: false
diff --git a/actionpack/test/fixtures/helpers/abc_helper.rb b/actionpack/test/fixtures/helpers/abc_helper.rb
index 7104ff3730..cf2774bb5f 100644
--- a/actionpack/test/fixtures/helpers/abc_helper.rb
+++ b/actionpack/test/fixtures/helpers/abc_helper.rb
@@ -1,5 +1,3 @@
module AbcHelper
def bare_a() end
- def bare_b() end
- def bare_c() end
end
diff --git a/actionpack/test/fixtures/helpers/helpery_test_helper.rb b/actionpack/test/fixtures/helpers/helpery_test_helper.rb
deleted file mode 100644
index a4f2951efa..0000000000
--- a/actionpack/test/fixtures/helpers/helpery_test_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module HelperyTestHelper
- def helpery_test
- "Default"
- end
-end
diff --git a/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb b/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
index 057dc40cca..93348f4647 100644
--- a/actionpack/test/journey/router/utils_test.rb
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -15,6 +15,14 @@ module ActionDispatch
def test_uri_unescape
assert_equal "a/b c+d", Utils.unescape_uri("a%2Fb%20c+d")
end
+
+ def test_normalize_path_not_greedy
+ assert_equal "/foo%20bar%20baz", Utils.normalize_path("/foo%20bar%20baz")
+ end
+
+ def test_normalize_path_uppercase
+ assert_equal "/foo%AAbar%AAbaz", Utils.normalize_path("/foo%aabar%aabaz")
+ end
end
end
end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index 38abb16d08..08af187311 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -96,88 +96,6 @@ class Comment
attr_accessor :body
end
-class Tag
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- attr_reader :post_id
- def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
- def to_key; id ? [id] : nil end
- def save; @id = 1; @post_id = 1 end
- def persisted?; @id.present? end
- def to_param; @id; end
- def value
- @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
- end
-
- attr_accessor :relevances
- def relevances_attributes=(attributes); end
-
-end
-
-class CommentRelevance
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- attr_reader :comment_id
- def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end
- def to_key; id ? [id] : nil end
- def save; @id = 1; @comment_id = 1 end
- def persisted?; @id.present? end
- def to_param; @id; end
- def value
- @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
- end
-end
-
-class Sheep
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- def to_key; id ? [id] : nil end
- def save; @id = 1 end
- def new_record?; @id.nil? end
- def name
- @id.nil? ? 'new sheep' : "sheep ##{@id}"
- end
-end
-
-
-class TagRelevance
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- attr_reader :id
- attr_reader :tag_id
- def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end
- def to_key; id ? [id] : nil end
- def save; @id = 1; @tag_id = 1 end
- def persisted?; @id.present? end
- def to_param; @id; end
- def value
- @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
- end
-end
-
-class Author < Comment
- attr_accessor :post
- def post_attributes=(attributes); end
-end
-
-class HashBackedAuthor < Hash
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- def persisted?; false; end
-
- def name
- "hash backed author"
- end
-end
-
module Blog
def self.use_relative_model_naming?
true
@@ -193,21 +111,8 @@ module Blog
end
end
-class ArelLike
- def to_ary
- true
- end
- def each
- a = Array.new(2) { |id| Comment.new(id + 1) }
- a.each { |i| yield i }
- end
-end
-
class RenderJsonTestException < Exception
def to_json(options = nil)
return { :error => self.class.name, :message => self.to_s }.to_json
end
end
-
-class Car < Struct.new(:color)
-end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 6240dc6ac6..4adf1dbd8f 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,7 +1,52 @@
-* Only cache template digests if `config.cache_template_loading` id true.
+* Ensure ActionView::Digestor.cache is correctly cleaned up when
+ combining recursive templates with ActionView::Resolver.caching = false
+
+ *wyaeld*
+
+* Fix `collection_check_boxes` generated hidden input to use the name attribute provided
+ in the options hash.
+
+ *Angel N. Sciortino*
+
+* Fix some edge cases for AV `select` helper with `:selected` option
+
+ *Bogdan Gusiev*
+
+* Ability to pass block to `select` helper
+
+ <%= select(report, "campaign_ids") do %>
+ <% available_campaigns.each do |c| -%>
+ <%= content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json }) %>
+ <% end -%>
+ <% end -%>
+
+ *Bogdan Gusiev*
+
+* Handle `:namespace` form option in collection labels
+
+ *Vasiliy Ermolovich*
+
+* Fix `form_for` when both `namespace` and `as` options are present
+
+ `as` option no longer overwrites `namespace` option when generating
+ html id attribute of the form element
+
+ *Adam Niedzielski*
+
+* Fix `excerpt` when `:separator` is `nil`.
+
+ *Paul Nikitochkin*
+
+* Only cache template digests if `config.cache_template_loading` is true.
*Josh Lauer*, *Justin Ridgewell*
+* Fixed a bug where the lookup details were not being taken into account
+ when caching the digest of a template - changes to the details now
+ cause a different cache key to be used.
+
+ *Daniel Schierbeck*
+
* Added an `extname` hash option for `javascript_include_tag` method.
Before:
diff --git a/actionview/RUNNING_UNIT_TESTS.rdoc b/actionview/RUNNING_UNIT_TESTS.rdoc
index a0c35e1810..104b3e288d 100644
--- a/actionview/RUNNING_UNIT_TESTS.rdoc
+++ b/actionview/RUNNING_UNIT_TESTS.rdoc
@@ -18,7 +18,7 @@ which can be further narrowed down to one test:
== Dependency on Active Record and database setup
-Test cases in the test/active_record/ directory depend on having
+Test cases in the test/activerecord/ directory depend on having
activerecord and sqlite installed. If Active Record is not in
actionview/../activerecord directory, or the sqlite rubygem is not installed,
these tests are skipped.
diff --git a/actionview/Rakefile b/actionview/Rakefile
index e1cb4b5be0..d56fe9ea76 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -11,7 +11,7 @@ task :test => ["test:template", "test:integration:action_pack", "test:integratio
namespace :test do
task :isolated do
- Dir.glob("test/{active_record,template}/**/*_test.rb").all? do |file|
+ Dir.glob("test/{actionpack,activerecord,template}/**/*_test.rb").all? do |file|
sh(Gem.ruby, '-w', '-Ilib:test', file)
end or raise "Failures"
end
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 87cb568300..cdac074973 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
s.name = 'actionview'
s.version = version
s.summary = 'Rendering framework putting the V in MVC (part of Rails).'
- s.description = ''
+ s.description = 'Simple, battle-tested conventions and helpers for building web pages.'
s.required_ruby_version = '>= 1.9.3'
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 95c3200c81..5570e2a8dc 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -10,7 +10,10 @@ module ActionView
class << self
def digest(name, format, finder, options = {})
- cache_key = ([name, format] + Array.wrap(options[:dependencies])).join('.')
+ details_key = finder.details_key.hash
+ dependencies = Array.wrap(options[:dependencies])
+ cache_key = ([name, details_key, format] + dependencies).join('.')
+
# this is a correctly done double-checked locking idiom
# (ThreadSafe::Cache's lookups have volatile semantics)
@@cache[cache_key] || @@digest_monitor.synchronize do
@@ -32,13 +35,13 @@ module ActionView
Digestor
end
+ digest = klass.new(name, format, finder, options).digest
# Store the actual digest if config.cache_template_loading is true
- klass.new(name, format, finder, options).digest.tap do |digest|
- @@cache[cache_key] = digest if ActionView::Resolver.caching?
- end
- rescue Exception
- @@cache.delete_pair(cache_key, false) if pre_stored # something went wrong, make sure not to corrupt the @@cache
- raise
+ @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
+ digest
+ ensure
+ # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
+ @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
end
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index a13d0021ea..e2430140c2 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -224,14 +224,14 @@ module ActionView
#
# ==== Examples
#
- # image_tag('rails.png')
- # # => <img alt="Rails" src="/assets/rails.png" />
+ # image_alt('rails.png')
+ # # => Rails
#
- # image_tag('hyphenated-file-name.png')
- # # => <img alt="Hyphenated file name" src="/assets/hyphenated-file-name.png" />
+ # image_alt('hyphenated-file-name.png')
+ # # => Hyphenated file name
#
- # image_tag('underscored_file_name.png')
- # # => <img alt="Underscored file name" src="/assets/underscored_file_name.png" />
+ # image_alt('underscored_file_name.png')
+ # # => Underscored file name
def image_alt(src)
File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index 0b957adb91..c830ab23e3 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -143,9 +143,9 @@ module ActionView
"#{source}#{tail}"
end
- alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route
+ alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
- # Computes the full URL to a asset in the public directory. This
+ # Computes the full URL to an asset in the public directory. This
# will use +asset_path+ internally, so most of their behaviors
# will be the same.
def asset_url(source, options = {})
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 8e1aea50a9..d7e8e99200 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -531,7 +531,7 @@ module ActionView
# my_date = Time.now + 2.days
#
# # Generates a select field for days that defaults to the day for the date in my_date.
- # select_day(my_time)
+ # select_day(my_date)
#
# # Generates a select field for days that defaults to the number given.
# select_day(5)
@@ -541,7 +541,7 @@ module ActionView
#
# # Generates a select field for days that defaults to the day for the date in my_date
# # that is named 'due' rather than 'day'.
- # select_day(my_time, field_name: 'due')
+ # select_day(my_date, field_name: 'due')
#
# # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a
# # generic prompt.
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 8a4830d887..38d969ed0c 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -442,10 +442,11 @@ module ActionView
object = convert_to_model(object)
as = options[:as]
+ namespace = options[:namespace]
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
options[:html].reverse_merge!(
class: as ? "#{action}_#{as}" : dom_class(object, action),
- id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
+ id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
method: method
)
@@ -1172,7 +1173,7 @@ module ActionView
# methods in the +FormHelper+ module. This class, however, allows you to
# call methods with the model object you are building the form for.
#
- # You can create your own custom FormBuilder templates by subclasses this
+ # You can create your own custom FormBuilder templates by subclassing this
# class. For example:
#
# class MyFormBuilder < ActionView::Helpers::FormBuilder
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index fcd151ac32..4347983bad 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -128,6 +128,15 @@ module ActionView
# or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
#
+ # A block can be passed to +select+ to customize how the options tags will be rendered. This
+ # is useful when the options tag has complex attributes.
+ #
+ # select(report, "campaign_ids") do
+ # available_campaigns.each do |c|
+ # content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json })
+ # end
+ # end
+ #
# ==== Gotcha
#
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
@@ -152,8 +161,8 @@ module ActionView
# In case if you don't want the helper to generate this hidden field you can specify
# <tt>include_hidden: false</tt> option.
#
- def select(object, method, choices, options = {}, html_options = {})
- Tags::Select.new(object, method, self, choices, options, html_options).render
+ def select(object, method, choices = nil, options = {}, html_options = {}, &block)
+ Tags::Select.new(object, method, self, choices, options, html_options, &block).render
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -766,8 +775,8 @@ module ActionView
# <% end %>
#
# Please refer to the documentation of the base helper for details.
- def select(method, choices, options = {}, html_options = {})
- @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
+ def select(method, choices = nil, options = {}, html_options = {}, &block)
+ @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options), &block)
end
# Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index 732f35643a..11e41ec056 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -114,7 +114,7 @@ module ActionView
# cdata_section("hello]]>world")
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
def cdata_section(content)
- splitted = content.gsub(']]>', ']]]]><![CDATA[>')
+ splitted = content.to_s.gsub(']]>', ']]]]><![CDATA[>')
"<![CDATA[#{splitted}]]>".html_safe
end
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 3fe3f4e9df..8607da301c 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -119,7 +119,8 @@ module ActionView
html_options = html_options.stringify_keys
add_default_name_and_id(html_options)
options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
- select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+ value = options.fetch(:selected) { value(object) }
+ select = content_tag("select", add_options(option_tags, options, value), html_options)
if html_options["multiple"] && options.fetch(:include_hidden, true)
tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 52006d856b..9b77ebeb1b 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -27,7 +27,8 @@ module ActionView
# Append a hidden field to make sure something will be sent back to the
# server if all check boxes are unchecked.
- hidden = @template_object.hidden_field_tag("#{tag_name}[]", "", :id => nil)
+ hidden_name = @html_options[:name] || "#{tag_name}[]"
+ hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil)
rendered_collection + hidden
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
index 388dcf1f13..787039c82e 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -18,7 +18,8 @@ module ActionView
end
def label(label_html_options={}, &block)
- @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block)
+ html_options = label_html_options.merge(@input_html_options)
+ @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 35d3ba8434..180aa9ac27 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -30,6 +30,7 @@ module ActionView
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options.delete("namespace")
+ options.delete("multiple")
options["for"] = name_and_id["id"] unless options.key?("for")
if block_given?
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index d64e2f68ef..00881d9978 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -3,8 +3,9 @@ module ActionView
module Tags # :nodoc:
class Select < Base # :nodoc:
def initialize(object_name, method_name, template_object, choices, options, html_options)
- @choices = choices
+ @choices = block_given? ? template_object.capture { yield } : choices
@choices = @choices.to_a if @choices.is_a?(Range)
+
@html_options = html_options
super(object_name, method_name, template_object, options)
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 3fc64fa8a5..c23d605c5f 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -150,7 +150,7 @@ module ActionView
def excerpt(text, phrase, options = {})
return unless text && phrase
- separator = options.fetch(:separator, "")
+ separator = options[:separator] || ''
phrase = Regexp.escape(phrase)
regex = /#{phrase}/i
@@ -171,7 +171,8 @@ module ActionView
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
- prefix + (first_part + separator + phrase + separator + second_part).strip + postfix
+ affix = [first_part, separator, phrase, separator, second_part].join.strip
+ [prefix, affix, postfix].join
end
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 1920a94567..56dd7a4390 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -92,8 +92,9 @@ module ActionView
# ==== Data attributes
#
# * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript
- # driver to prompt with the question specified. If the user accepts, the link is
- # processed normally, otherwise no action is taken.
+ # driver to prompt with the question specified (in this case, the
+ # resulting text would be <tt>question?</tt>. If the user accepts, the
+ # link is processed normally, otherwise no action is taken.
# * <tt>:disable_with</tt> - Value of this parameter will be
# used as the value for a disabled version of the submit
# button when the form is submitted. This feature is provided
@@ -213,6 +214,7 @@ module ActionView
# * <tt>:form</tt> - This hash will be form attributes
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
# be placed
+ # * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form.
#
# ==== Data attributes
#
@@ -287,6 +289,7 @@ module ActionView
url = options.is_a?(String) ? options : url_for(options)
remote = html_options.delete('remote')
+ params = html_options.delete('params')
method = html_options.delete('method').to_s
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe
@@ -310,6 +313,11 @@ module ActionView
end
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
+ if params
+ params.each do |param_name, value|
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
+ end
+ end
content_tag('form', content_tag('div', inner_tags), form_options)
end
@@ -454,7 +462,7 @@ module ActionView
html_options, name = name, nil if block_given?
html_options = (html_options || {}).stringify_keys
- extras = %w{ cc bcc body subject }.map { |item|
+ extras = %w{ cc bcc body subject }.map! { |item|
option = html_options.delete(item) || next
"#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 4113448af0..c2783f6377 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -48,5 +48,9 @@ module ActionView
ActionMailer::Base.send(:include, ActionView::Layouts)
end
end
+
+ rake_tasks do
+ load "action_view/tasks/dependencies.rake"
+ end
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index db9c4ef501..18a0788f8e 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -77,27 +77,6 @@ module ActionView
@_view_renderer ||= ActionView::Renderer.new(lookup_context)
end
- # Render template to response_body
- # :api: public
- def render(*args, &block)
- options = _normalize_render(*args, &block)
- self.response_body = render_to_body(options)
- end
-
- # Raw rendering of a template to a string.
- # :api: public
- def render_to_string(*args, &block)
- options = _normalize_render(*args, &block)
- render_to_body(options)
- end
-
- # Raw rendering of a template.
- # :api: public
- def render_to_body(options = {})
- _process_options(options)
- _render_template(options)
- end
-
# Find and renders a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
@@ -105,22 +84,22 @@ module ActionView
view_renderer.render(view_context, options)
end
- def rendered_format
- Mime[lookup_context.rendered_format]
+ def render_to_body(options = {})
+ _process_options(options)
+ _render_template(options)
end
- def default_protected_instance_vars
- super.concat([:@_view_context_class, :@_view_renderer, :@_lookup_context])
+ def rendered_format
+ Mime[lookup_context.rendered_format]
end
private
- # Normalize args and options.
- # :api: private
- def _normalize_render(*args, &block)
- options = _normalize_args(*args, &block)
- _normalize_options(options)
- options
+ # Assign the rendered format to lookup context.
+ def _process_format(format) #:nodoc:
+ super
+ lookup_context.formats = [format.to_sym]
+ lookup_context.rendered_format = lookup_context.formats.first
end
# Normalize args by converting render "foo" to render :action => "foo" and
diff --git a/actionview/lib/action_view/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake
new file mode 100644
index 0000000000..1b9426c0e5
--- /dev/null
+++ b/actionview/lib/action_view/tasks/dependencies.rake
@@ -0,0 +1,17 @@
+namespace :cache_digests do
+ desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
+ task :nested_dependencies => :environment do
+ abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
+ template, format = ENV['TEMPLATE'].split(".")
+ format ||= :html
+ puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).nested_dependencies
+ end
+
+ desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
+ task :dependencies => :environment do
+ abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
+ template, format = ENV['TEMPLATE'].split(".")
+ format ||= :html
+ puts JSON.pretty_generate ActionView::Digestor.new(template, format, ApplicationController.new.lookup_context).dependencies
+ end
+end
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index e2c50fec47..9b0619f1aa 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -142,7 +142,7 @@ module ActionView
compile!(view)
view.send(method_name, locals, buffer, &block)
end
- rescue Exception => e
+ rescue => e
handle_render_error(view, e)
end
@@ -294,7 +294,7 @@ module ActionView
begin
mod.module_eval(source, identifier, 0)
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
- rescue Exception => e # errors from template code
+ rescue => e # errors from template code
if logger = (view && view.logger)
logger.debug "ERROR: compiling #{method_name} RAISED #{e}"
logger.debug "Function body: #{source}"
diff --git a/actionview/test/lib/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb
index c6e7a523b9..c6e7a523b9 100644
--- a/actionview/test/lib/controller/view_paths_test.rb
+++ b/actionview/test/actionpack/controller/view_paths_test.rb
diff --git a/actionview/test/fixtures/helpers/abc_helper.rb b/actionview/test/fixtures/helpers/abc_helper.rb
index 7104ff3730..cf2774bb5f 100644
--- a/actionview/test/fixtures/helpers/abc_helper.rb
+++ b/actionview/test/fixtures/helpers/abc_helper.rb
@@ -1,5 +1,3 @@
module AbcHelper
def bare_a() end
- def bare_b() end
- def bare_c() end
end
diff --git a/actionview/test/fixtures/helpers/fun/games_helper.rb b/actionview/test/fixtures/helpers/fun/games_helper.rb
deleted file mode 100644
index 3b7adce086..0000000000
--- a/actionview/test/fixtures/helpers/fun/games_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Fun
- module GamesHelper
- def stratego() "Iz guuut!" end
- end
-end \ No newline at end of file
diff --git a/actionview/test/fixtures/helpers/fun/pdf_helper.rb b/actionview/test/fixtures/helpers/fun/pdf_helper.rb
deleted file mode 100644
index 0171be8500..0000000000
--- a/actionview/test/fixtures/helpers/fun/pdf_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Fun
- module PdfHelper
- def foobar() 'baz' end
- end
-end
diff --git a/actionview/test/fixtures/helpers/just_me_helper.rb b/actionview/test/fixtures/helpers/just_me_helper.rb
deleted file mode 100644
index b140a7b9b4..0000000000
--- a/actionview/test/fixtures/helpers/just_me_helper.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module JustMeHelper
- def me() "mine!" end
-end \ No newline at end of file
diff --git a/actionview/test/fixtures/helpers/me_too_helper.rb b/actionview/test/fixtures/helpers/me_too_helper.rb
deleted file mode 100644
index ce56042143..0000000000
--- a/actionview/test/fixtures/helpers/me_too_helper.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module MeTooHelper
- def me() "me too!" end
-end \ No newline at end of file
diff --git a/actionview/test/lib/controller/fake_controllers.rb b/actionview/test/lib/controller/fake_controllers.rb
deleted file mode 100644
index 1a2863b689..0000000000
--- a/actionview/test/lib/controller/fake_controllers.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-class ContentController < ActionController::Base; end
-
-module Admin
- class AccountsController < ActionController::Base; end
- class PostsController < ActionController::Base; end
- class StuffController < ActionController::Base; end
- class UserController < ActionController::Base; end
- class UsersController < ActionController::Base; end
-end
-
-module Api
- class UsersController < ActionController::Base; end
- class ProductsController < ActionController::Base; end
-end
-
-class AccountController < ActionController::Base; end
-class ArchiveController < ActionController::Base; end
-class ArticlesController < ActionController::Base; end
-class BarController < ActionController::Base; end
-class BlogController < ActionController::Base; end
-class BooksController < ActionController::Base; end
-class CarsController < ActionController::Base; end
-class CcController < ActionController::Base; end
-class CController < ActionController::Base; end
-class FooController < ActionController::Base; end
-class GeocodeController < ActionController::Base; end
-class NewsController < ActionController::Base; end
-class NotesController < ActionController::Base; end
-class PagesController < ActionController::Base; end
-class PeopleController < ActionController::Base; end
-class PostsController < ActionController::Base; end
-class SubpathBooksController < ActionController::Base; end
-class SymbolsController < ActionController::Base; end
-class UserController < ActionController::Base; end
-class UsersController < ActionController::Base; end
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index c6608e214a..779a7fb53c 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -15,6 +15,16 @@ end
class FixtureFinder
FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor"
+ attr_reader :details
+
+ def initialize
+ @details = {}
+ end
+
+ def details_key
+ details.hash
+ end
+
def find(logical_name, keys, partial, options)
FixtureTemplate.new("digestor/#{partial ? logical_name.gsub(%r|/([^/]+)$|, '/_\1') : logical_name}.#{options[:formats].first}.erb")
end
@@ -140,6 +150,20 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_details_are_included_in_cache_key
+ # Cache the template digest.
+ old_digest = digest("events/_event")
+
+ # Change the template; the cached digest remains unchanged.
+ change_template("events/_event")
+
+ # The details are changed, so a new cache key is generated.
+ finder.details[:foo] = "bar"
+
+ # The cache is busted.
+ assert_not_equal old_digest, digest("events/_event")
+ end
+
def test_extra_whitespace_in_render_partial
assert_digest_difference("messages/edit") do
change_template("messages/_form")
@@ -193,6 +217,31 @@ class TemplateDigestorTest < ActionView::TestCase
ActionView::Resolver.caching = resolver_before
end
+ def test_digest_cache_cleanup_with_recursion
+ first_digest = digest("level/_recursion")
+ second_digest = digest("level/_recursion")
+
+ assert first_digest
+
+ # If the cache is cleaned up correctly, subsequent digests should return the same
+ assert_equal first_digest, second_digest
+ end
+
+ def test_digest_cache_cleanup_with_recursion_and_template_caching_off
+ resolver_before = ActionView::Resolver.caching
+ ActionView::Resolver.caching = false
+
+ first_digest = digest("level/_recursion")
+ second_digest = digest("level/_recursion")
+
+ assert first_digest
+
+ # If the cache is cleaned up correctly, subsequent digests should return the same
+ assert_equal first_digest, second_digest
+ ensure
+ ActionView::Resolver.caching = resolver_before
+ end
+
private
def assert_logged(message)
old_logger = ActionView::Base.logger
@@ -220,7 +269,11 @@ class TemplateDigestorTest < ActionView::TestCase
end
def digest(template_name, options={})
- ActionView::Digestor.digest(template_name, :html, FixtureFinder.new, options)
+ ActionView::Digestor.digest(template_name, :html, finder, options)
+ end
+
+ def finder
+ @finder ||= FixtureFinder.new
end
def change_template(template_name)
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index bc9c21dfd3..d28e4aeb48 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -17,14 +17,14 @@ class FormCollectionsHelperTest < ActionView::TestCase
end
# COLLECTION RADIO BUTTONS
- test 'collection radio accepts a collection and generate inputs from value method' do
+ test 'collection radio accepts a collection and generates inputs from value method' do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
assert_select 'input[type=radio][value=true]#user_active_true'
assert_select 'input[type=radio][value=false]#user_active_false'
end
- test 'collection radio accepts a collection and generate inputs from label method' do
+ test 'collection radio accepts a collection and generates inputs from label method' do
with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
assert_select 'label[for=user_active_true]', 'true'
@@ -38,7 +38,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'label[for=user_active_no]', 'No'
end
- test 'colection radio should sanitize collection values for labels correctly' do
+ test 'collection radio should sanitize collection values for labels correctly' do
with_collection_radio_buttons :user, :name, ['$0.99', '$1.99'], :to_s, :to_s
assert_select 'label[for=user_name_099]', '$0.99'
assert_select 'label[for=user_name_199]', '$1.99'
@@ -179,6 +179,13 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 1
end
+ test 'collection check boxes generates a hidden field using the given :name in :html_options' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name, {}, {name: "user[other_category_ids][]"}
+
+ assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1
+ end
+
test 'collection check boxes accepts a collection and generate a serie of checkboxes with labels for label method' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
with_collection_check_boxes :user, :category_ids, collection, :id, :name
@@ -194,7 +201,7 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'label[for=user_active_no]', 'No'
end
- test 'colection check box should sanitize collection values for labels correctly' do
+ test 'collection check box should sanitize collection values for labels correctly' do
with_collection_check_boxes :user, :name, ['$0.99', '$1.99'], :to_s, :to_s
assert_select 'label[for=user_name_099]', '$0.99'
assert_select 'label[for=user_name_199]', '$1.99'
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 8cca43d7ca..3e8a2468ed 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -1282,6 +1282,24 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_with_namespace_and_with_collection_radio_buttons
+ post = Post.new
+ def post.active; false; end
+
+ form_for(post, namespace: 'foo') do |f|
+ concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts", "foo_new_post", "new_post") do
+ "<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" +
+ "<label for='foo_post_active_true'>true</label>" +
+ "<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" +
+ "<label for='foo_post_active_false'>false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_collection_check_boxes
post = Post.new
def post.tag_ids; [1, 3]; end
@@ -1361,6 +1379,24 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_with_namespace_and_with_collection_check_boxes
+ post = Post.new
+ def post.tag_ids; [1]; end
+ collection = [[1, "Tag 1"]]
+
+ form_for(post, namespace: 'foo') do |f|
+ concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
+ end
+
+ expected = whole_form("/posts", "foo_new_post", "new_post") do
+ "<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "<label for='foo_post_tag_ids_1'>Tag 1</label>" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_file_field_generate_multipart
Post.send :attr_accessor, :file
@@ -1681,6 +1717,18 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_namespace_and_as_option
+ form_for(@post, namespace: 'namespace', as: 'custom_name') do |f|
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form('/posts/123', 'namespace_edit_custom_name', 'edit_custom_name', method: 'patch') do
+ "<input id='namespace_custom_name_title' name='custom_name[title]' type='text' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_two_form_for_with_namespace
form_for(@post, namespace: 'namespace_1') do |f|
concat f.label(:title)
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 3ec138b639..50e9d132a7 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'tzinfo'
class Map < Hash
def category
@@ -7,8 +6,6 @@ class Map < Hash
end
end
-TZInfo::Timezone.cattr_reader :loaded_zones
-
class FormOptionsHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormOptionsHelper
@@ -22,7 +19,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def setup
@fake_timezones = %w(A B C D E).map do |id|
- tz = TZInfo::Timezone.loaded_zones[id] = stub(:name => id, :to_s => id)
+ tz = stub(:name => id, :to_s => id)
ActiveSupport::TimeZone.stubs(:[]).with(id).returns(tz)
tz
end
@@ -559,6 +556,21 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_under_fields_for_with_block
+ @post = Post.new
+
+ output_buffer = fields_for :post, @post do |f|
+ concat(f.select(:category) do
+ concat content_tag(:option, "hello world")
+ end)
+ end
+
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option>hello world</option></select>",
+ output_buffer
+ )
+ end
+
def test_select_with_multiple_to_add_hidden_input
output_buffer = select(:post, :category, "", {}, :multiple => true)
assert_dom_equal(
@@ -786,6 +798,22 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_not_existing_method_with_selected_value
+ @post = Post.new
+ assert_dom_equal(
+ "<select id=\"post_locale\" name=\"post[locale]\"><option value=\"en\">en</option>\n<option value=\"ru\" selected=\"selected\">ru</option></select>",
+ select("post", "locale", %w( en ru ), :selected => 'ru')
+ )
+ end
+
+ def test_select_with_prompt_and_selected_value
+ @post = Post.new
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"one\">one</option>\n<option selected=\"selected\" value=\"two\">two</option></select>",
+ select("post", "category", %w( one two ), :selected => 'two', :prompt => true)
+ )
+ end
+
def test_select_with_disabled_array
@post = Post.new
@post.category = "<mus>"
diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb
index 97b1bad055..575eb9bd28 100644
--- a/actionview/test/template/resolver_patterns_test.rb
+++ b/actionview/test/template/resolver_patterns_test.rb
@@ -20,7 +20,7 @@ class ResolverPatternsTest < ActiveSupport::TestCase
assert_equal [:html], templates.first.formats
end
- def test_should_return_all_templates_when_ambigous_pattern
+ def test_should_return_all_templates_when_ambiguous_pattern
templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
assert_equal 2, templates.size, "expected two templates"
assert_equal "Another template!", templates[0].source
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index 802da5d566..fb016a52de 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -96,6 +96,10 @@ class TagHelperTest < ActionView::TestCase
assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
end
+ def test_cdata_section_with_string_conversion
+ assert_equal "<![CDATA[]]>", cdata_section(nil)
+ end
+
def test_cdata_section_splitted
assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]>", cdata_section("hello]]>world")
assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]]]><![CDATA[>again]]>", cdata_section("hello]]>world]]>again")
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index 1b2234f4e2..c2999fcb85 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -314,6 +314,9 @@ class TextHelperTest < ActionView::TestCase
options = { :separator => "\n", :radius => 1 }
assert_equal("...very\nvery long\nstring", excerpt("my very\nvery\nvery long\nstring", 'long', options))
+
+ assert_equal excerpt('This is a beautiful morning', 'a'),
+ excerpt('This is a beautiful morning', 'a', separator: nil)
end
def test_word_wrap
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index d512fa9913..deba33510a 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
+require 'minitest/mock'
class UrlHelperTest < ActiveSupport::TestCase
@@ -160,6 +161,13 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_button_to_with_params
+ assert_dom_equal(
+ %{<form action="http://www.example.com" class="button_to" method="post"><div><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></div></form>},
+ button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"})
+ )
+ end
+
def test_link_tag_with_straight_url
assert_dom_equal %{<a href="http://www.example.com">Hello</a>}, link_to("Hello", "http://www.example.com")
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 3d3c61ed1c..e8602ecbcf 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,18 @@
+* Fix `has_secure_password` to honor bcrypt-ruby's cost attribute.
+
+ *T.J. Schuck*
+
+* Updated the `ActiveModel::Dirty#changed_attributes` method to be indifferent between using
+ symbols and strings as keys.
+
+ *William Myers*
+
+* Added new API methods `reset_changes` and `changed_applied` to `ActiveModel::Dirty`
+ that control changes state. Previsously you needed to update internal
+ instance variables, but now API methods are available.
+
+ *Bogdan Gusiev*
+
* Fix has_secure_password. `password_confirmation` validations are triggered
even if no `password_confirmation` is set.
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 51655fe3da..11e755649c 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
s.name = 'activemodel'
s.version = version
s.summary = 'A toolkit for building modeling frameworks (part of Rails).'
- s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
+ s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing.'
s.required_ruby_version = '>= 1.9.3'
diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb
index c94cd17e18..b8e74acd5e 100644
--- a/activemodel/examples/validations.rb
+++ b/activemodel/examples/validations.rb
@@ -1,3 +1,4 @@
+require File.expand_path('../../../load_paths', __FILE__)
require 'active_model'
class Person
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index ea5ddf71de..c5f1b3f11a 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -14,13 +14,9 @@ module ActiveModel
# track.
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
# attribute.
- #
- # If you wish to also track previous changes on save or update, you need to
- # add:
- #
- # @previously_changed = changes
- #
- # inside of your save or update method.
+ # * Call <tt>changes_applied</tt> after the changes are persisted.
+ # * Call <tt>reset_changes</tt> when you want to reset the changes
+ # information.
#
# A minimal implementation could be:
#
@@ -39,8 +35,12 @@ module ActiveModel
# end
#
# def save
- # @previously_changed = changes
- # @changed_attributes.clear
+ # # do persistence work
+ # changes_applied
+ # end
+ #
+ # def reload!
+ # reset_changes
# end
# end
#
@@ -65,6 +65,12 @@ module ActiveModel
# person.changed? # => false
# person.name_changed? # => false
#
+ # Reset the changes:
+ #
+ # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
+ # person.reload!
+ # person.previous_changes # => {}
+ #
# Assigning the same value leaves the attribute unchanged:
#
# person.name = 'Bill'
@@ -129,7 +135,7 @@ module ActiveModel
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
- @previously_changed
+ @previously_changed ||= {}
end
# Returns a hash of the attributes with unsaved changes indicating their original
@@ -139,7 +145,7 @@ module ActiveModel
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def changed_attributes
- @changed_attributes ||= {}
+ @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
end
# Handle <tt>*_changed?</tt> for +method_missing+.
@@ -154,6 +160,18 @@ module ActiveModel
private
+ # Removes current changes and makes them accessible through +previous_changes+.
+ def changes_applied
+ @previously_changed = changes
+ @changed_attributes = {}
+ end
+
+ # Removes all dirty data: current changes and previous changes
+ def reset_changes
+ @previously_changed = {}
+ @changed_attributes = {}
+ end
+
# Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 8b9ac97bbb..7e694b5c50 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -20,9 +20,9 @@ module ActiveModel
# value to the password_confirmation attribute and the validation
# will not be triggered.
#
- # You need to add bcrypt-ruby (~> 3.1.0) to Gemfile to use #has_secure_password:
+ # You need to add bcrypt-ruby (~> 3.1.2) to Gemfile to use #has_secure_password:
#
- # gem 'bcrypt-ruby', '~> 3.1.0'
+ # gem 'bcrypt-ruby', '~> 3.1.2'
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
@@ -46,7 +46,6 @@ module ActiveModel
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
begin
- gem 'bcrypt-ruby', '~> 3.1.0'
require 'bcrypt'
rescue LoadError
$stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
@@ -103,7 +102,7 @@ module ActiveModel
def password=(unencrypted_password)
unless unencrypted_password.blank?
@password = unencrypted_password
- cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
end
end
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
index 1c35cb7c35..fd6cc1edb4 100644
--- a/activemodel/lib/active_model/validations/clusivity.rb
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -30,12 +30,18 @@ module ActiveModel
@delimiter ||= options[:in] || options[:within]
end
- # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
- # range for equality, which is slower but more accurate. <tt>Range#cover?</tt> uses
- # the previous logic of comparing a value with the range endpoints, which is fast
- # but is only accurate on numeric ranges.
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
+ # possible values in the range for equality, which is slower but more accurate.
+ # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
+ # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges.
def inclusion_method(enumerable)
- (enumerable.is_a?(Range) && enumerable.first.is_a?(Numeric)) ? :cover? : :include?
+ return :include? unless enumerable.is_a?(Range)
+ case enumerable.first
+ when Numeric, Time, DateTime
+ :cover?
+ else
+ :include?
+ end
end
end
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index ba45089cca..a90d0b1299 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -3,11 +3,12 @@ require "cases/helper"
class DirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Dirty
- define_attribute_methods :name, :color
+ define_attribute_methods :name, :color, :size
def initialize
@name = nil
@color = nil
+ @size = nil
end
def name
@@ -28,9 +29,17 @@ class DirtyTest < ActiveModel::TestCase
@color = val
end
+ def size
+ @size
+ end
+
+ def size=(val)
+ attribute_will_change!(:size) unless val == @size
+ @size = val
+ end
+
def save
- @previously_changed = changes
- @changed_attributes.clear
+ changes_applied
end
end
@@ -125,4 +134,9 @@ class DirtyTest < ActiveModel::TestCase
assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change
assert_equal @model.name_was, "Otto"
end
+
+ test "using attribute_will_change! with a symbol" do
+ @model.size = 1
+ assert @model.size_changed?
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 98e5c747d5..41d0b2263e 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -82,6 +82,14 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost
end
+ test "Password digest cost honors bcrypt cost attribute when min_cost is false" do
+ ActiveModel::SecurePassword.min_cost = false
+ BCrypt::Engine.cost = 5
+
+ @user.password = "secret"
+ assert_equal BCrypt::Engine.cost, @user.password_digest.cost
+ end
+
test "Password digest cost can be set to bcrypt min cost to speed up tests" do
ActiveModel::SecurePassword.min_cost = true
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index e4f5e61e91..bc185c737f 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -198,7 +198,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{"preferences":}, json
end
- test "custom as_json options should be extendible" do
+ test "custom as_json options should be extensible" do
def @contact.as_json(options = {}); super(options.merge(only: [:name])); end
json = @contact.to_json
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 41a4c33727..5049d6dd61 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -23,7 +23,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
- assert t.errors[:title].empty?
+ assert_empty t.errors[:title]
end
def test_if_validation_using_method_false
@@ -31,7 +31,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true_but_its_not)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
- assert t.errors[:title].empty?
+ assert_empty t.errors[:title]
end
def test_unless_validation_using_method_false
@@ -57,7 +57,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
- assert t.errors[:title].empty?
+ assert_empty t.errors[:title]
end
def test_if_validation_using_string_false
@@ -65,7 +65,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
- assert t.errors[:title].empty?
+ assert_empty t.errors[:title]
end
def test_unless_validation_using_string_false
@@ -93,7 +93,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
unless: Proc.new { |r| r.content.size > 4 })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
- assert t.errors[:title].empty?
+ assert_empty t.errors[:title]
end
def test_if_validation_using_block_false
@@ -102,7 +102,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
if: Proc.new { |r| r.title != "uhohuhoh"})
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
- assert t.errors[:title].empty?
+ assert_empty t.errors[:title]
end
def test_unless_validation_using_block_false
@@ -124,7 +124,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
t = Topic.new
assert t.invalid?, "A topic without a title should not be valid"
- assert t.errors[:author_name].empty?, "A topic without an 'important' title should not require an author"
+ assert_empty t.errors[:author_name], "A topic without an 'important' title should not require an author"
t.title = "Just a title"
assert t.valid?, "A topic with a basic title should be valid"
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 01a373d85d..8b90856869 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'cases/helper'
+require 'active_support/all'
require 'models/topic'
require 'models/person'
@@ -20,6 +21,27 @@ class InclusionValidationTest < ActiveModel::TestCase
assert Topic.new("title" => "bbb", "content" => "abc").valid?
end
+ def test_validates_inclusion_of_time_range
+ Topic.validates_inclusion_of(:created_at, in: 1.year.ago..Time.now)
+ assert Topic.new(title: 'aaa', created_at: 2.years.ago).invalid?
+ assert Topic.new(title: 'aaa', created_at: 3.months.ago).valid?
+ assert Topic.new(title: 'aaa', created_at: 37.weeks.from_now).invalid?
+ end
+
+ def test_validates_inclusion_of_date_range
+ Topic.validates_inclusion_of(:created_at, in: 1.year.until(Date.today)..Date.today)
+ assert Topic.new(title: 'aaa', created_at: 2.years.until(Date.today)).invalid?
+ assert Topic.new(title: 'aaa', created_at: 3.months.until(Date.today)).valid?
+ assert Topic.new(title: 'aaa', created_at: 37.weeks.since(Date.today)).invalid?
+ end
+
+ def test_validates_inclusion_of_date_time_range
+ Topic.validates_inclusion_of(:created_at, in: 1.year.until(DateTime.current)..DateTime.current)
+ assert Topic.new(title: 'aaa', created_at: 2.years.until(DateTime.current)).invalid?
+ assert Topic.new(title: 'aaa', created_at: 3.months.until(DateTime.current)).valid?
+ assert Topic.new(title: 'aaa', created_at: 37.weeks.since(DateTime.current)).invalid?
+ end
+
def test_validates_inclusion_of
Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ))
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
index c9af78f595..1411a093e9 100644
--- a/activemodel/test/models/topic.rb
+++ b/activemodel/test/models/topic.rb
@@ -6,7 +6,7 @@ class Topic
super | [ :message ]
end
- attr_accessor :title, :author_name, :content, :approved
+ attr_accessor :title, :author_name, :content, :approved, :created_at
attr_accessor :after_validation_performed
after_validation :perform_after_validation
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index c7928e503b..53dc1374b4 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,279 @@
-* Fix PredicateBuilder so polymorhic association keys in `where` clause can
- also accept not only `ActiveRecord::Base` direct descendances (decorated
+* ActiveRecord::Base#attribute_for_inspect now truncates long arrays (more than 10 elements)
+
+ *Jan Bernacki*
+
+* Allow for the name of the schema_migrations table to be configured.
+
+ *Jerad Phelps*
+
+* Do not add to scope includes values from through associations.
+ Fixed bug when providing `includes` in through association scope, and fetching targets.
+
+ Example:
+ class Vendor < ActiveRecord::Base
+ has_many :relationships, -> { includes(:user) }
+ has_many :users, through: :relationships
+ end
+
+ vendor = Vendor.first
+
+ # Before
+
+ vendor.users.to_a # => Raises exception: not found `:user` for `User`
+
+ # After
+
+ vendor.users.to_a # => No exception is raised
+
+
+ Fixes #12242, #9517, #10240.
+
+ *Paul Nikitochkin*
+
+* Type cast json values on write, so that the value is consistent
+ with reading from the database.
+
+ Example:
+
+ x = JsonDataType.new tags: {"string" => "foo", :symbol => :bar}
+
+ # Before:
+ x.tags # => {"string" => "foo", :symbol => :bar}
+
+ # After:
+ x.tags # => {"string" => "foo", "symbol" => "bar"}
+
+ *Severin Schoepke*
+
+* `ActiveRecord::Store` works together with PG `hstore` columns.
+ Fixes #12452.
+
+ *Yves Senn*
+
+* Fix bug where `ActiveRecord::Store` used a global `Hash` to keep track of
+ all registered `stored_attributes`. Now every subclass of
+ `ActiveRecord::Base` has it's own `Hash`.
+
+ *Yves Senn*
+
+* Save `has_one` association when primary key is manually set.
+
+ Fixes #12302.
+
+ *Lauro Caetano*
+
+* Allow any version of BCrypt when using `has_secure_password`.
+
+ *Mike Perham*
+
+* Sub-query generated for `Relation` passed as array condition did not take in account
+ bind values and have invalid syntax.
+
+ Generate sub-query with inline bind values.
+
+ Fixes #12586.
+
+ *Paul Nikitochkin*
+
+* Fix a bug where rake db:structure:load crashed when the path contained
+ spaces.
+
+ *Kevin Mook*
+
+* `ActiveRecord::QueryMethods#unscope` unscopes negative equality
+
+ Allows you to call `#unscope` on a relation with negative equality
+ operators, i.e. `Arel::Nodes::NotIn` and `Arel::Nodes::NotEqual` that have
+ been generated through the use of `where.not`.
+
+ *Eric Hankins*
+
+* Raise an exception when model without primary key calls `.find_with_ids`.
+
+ *Shimpei Makimoto*
+
+* Make `Relation#empty?` use `exists?` instead of `count`.
+
+ *Szymon Nowak*
+
+* `rake db:structure:dump` no longer crashes when the port was specified as `Fixnum`.
+
+ *Kenta Okamoto*
+
+* `NullRelation#pluck` takes a list of columns
+
+ The method signature in `NullRelation` was updated to mimic that in
+ `Calculations`.
+
+ *Derek Prior*
+
+* `scope_chain` should not be mutated for other reflections.
+
+ Currently `scope_chain` uses same array for building different
+ `scope_chain` for different associations. During processing
+ these arrays are sometimes mutated and because of in-place
+ mutation the changed `scope_chain` impacts other reflections.
+
+ Fix is to dup the value before adding to the `scope_chain`.
+
+ Fixes #3882.
+
+ *Neeraj Singh*
+
+* Prevent the inversed association from being reloaded on save.
+
+ Fixes #9499.
+
+ *Dmitry Polushkin*
+
+* Generate subquery for `Relation` if it passed as array condition for `where`
+ method.
+
+ Example:
+
+ # Before
+ Blog.where('id in (?)', Blog.where(id: 1))
+ # => SELECT "blogs".* FROM "blogs" WHERE "blogs"."id" = 1
+ # => SELECT "blogs".* FROM "blogs" WHERE (id IN (1))
+
+ # After
+ Blog.where('id in (?)', Blog.where(id: 1).select(:id))
+ # => SELECT "blogs".* FROM "blogs"
+ # WHERE "blogs"."id" IN (SELECT "blogs"."id" FROM "blogs" WHERE "blogs"."id" = 1)
+
+ Fixes #12415.
+
+ *Paul Nikitochkin*
+
+* For missed association exception message
+ which is raised in `ActiveRecord::Associations::Preloader` class
+ added owner record class name in order to simplify to find problem code.
+
+ *Paul Nikitochkin*
+
+* `has_and_belongs_to_many` is now transparently implemented in terms of
+ `has_many :through`. Behavior should remain the same, if not, it is a bug.
+
+* `create_savepoint`, `rollback_to_savepoint` and `release_savepoint` accept
+ a savepoint name.
+
+ *Yves Senn*
+
+* Make `next_migration_number` accessible for third party generators.
+
+ *Yves Senn*
+
+* Objects instantiated using a null relationship will now retain the
+ attributes of the where clause.
+
+ Fixes #11676, #11675, #11376.
+
+ *Paul Nikitochkin*, *Peter Brown*, *Nthalk*
+
+* Fixed `ActiveRecord::Associations::CollectionAssociation#find`
+ when using `has_many` association with `:inverse_of` and finding an array of one element,
+ it should return an array of one element too.
+
+ *arthurnn*
+
+* Callbacks on has_many should access the in memory parent if a inverse_of is set.
+
+ *arthurnn*
+
+* `ActiveRecord::ConnectionAdapters.string_to_time` respects
+ string with timezone (e.g. Wed, 04 Sep 2013 20:30:00 JST).
+
+ Fixes #12278.
+
+ *kennyj*
+
+* Calling `update_attributes` will now throw an `ArgumentError` whenever it
+ gets a `nil` argument. More specifically, it will throw an error if the
+ argument that it gets passed does not respond to to `stringify_keys`.
+
+ Example:
+
+ @my_comment.update_attributes(nil) # => raises ArgumentError
+
+ *John Wang*
+
+* Deprecate `quoted_locking_column` method, which isn't used anywhere.
+
+ *kennyj*
+
+* Migration dump UUID default functions to schema.rb.
+
+ Fixes #10751.
+
+ *kennyj*
+
+* Fixed a bug in `ActiveRecord::Associations::CollectionAssociation#find_by_scan`
+ when using `has_many` association with `:inverse_of` option and UUID primary key.
+
+ Fixes #10450.
+
+ *kennyj*
+
+* ActiveRecord::Base#<=> has been removed. Primary keys may not be in order,
+ or even be numbers, so sorting by id doesn't make sense. Please use `sort_by`
+ and specify the attribute you wish to sort with. For example, change:
+
+ Post.all.to_a.sort
+
+ to:
+
+ Post.all.to_a.sort_by(&:id)
+
+ *Aaron Patterson*
+
+* Fix: joins association, with defined in the scope block constraints by using several
+ where constraints and at least of them is not `Arel::Nodes::Equality`,
+ generates invalid SQL expression.
+
+ Fixes #11963.
+
+ *Paul Nikitochkin*
+
+* Deprecate the delegation of Array bang methods for associations.
+ To use them, instead first call `#to_a` on the association to access the
+ array to be acted on.
+
+ *Ben Woosley*
+
+* `CollectionAssociation#first`/`#last` (e.g. `has_many`) use a `LIMIT`ed
+ query to fetch results rather than loading the entire collection.
+
+ *Lann Martin*
+
+* Make possible to run SQLite rake tasks without the `Rails` constant defined.
+
+ *Damien Mathieu*
+
+* Allow Relation#from to accept other relations with bind values.
+
+ *Ryan Wallace*
+
+* Fix inserts with prepared statements disabled.
+
+ Fixes #12023.
+
+ *Rafael Mendonça França*
+
+* Setting a has_one association on a new record no longer causes an empty
+ transaction.
+
+ *Dylan Thacker-Smith*
+
+* Fix `AR::Relation#merge` sometimes failing to preserve `readonly(false)` flag.
+
+ *thedarkone*
+
+* Re-use `order` argument pre-processing for `reorder`.
+
+ *Paul Nikitochkin*
+
+* Fix PredicateBuilder so polymorphic association keys in `where` clause can
+ accept objects other than direct descendants of `ActiveRecord::Base` (decorated
models, for example).
*Mikhail Dieterle*
@@ -78,6 +352,12 @@
*Yves Senn*
+* Fixes bug when using includes combined with select, the select statement was overwritten.
+
+ Fixes #11773.
+
+ *Edo Balvers*
+
* Load fixtures from linked folders.
*Kassio Borges*
@@ -129,13 +409,13 @@
to allow the connection adapter to properly determine how to quote the value. This was
affecting certain databases that use specific column types.
- Fixes: #6763
+ Fixes #6763.
*Alfred Wong*
* rescue from all exceptions in `ConnectionManagement#call`
- Fixes #11497
+ Fixes #11497.
As `ActiveRecord::ConnectionAdapters::ConnectionManagement` middleware does
not rescue from Exception (but only from StandardError), the Connection
@@ -157,27 +437,27 @@
* Remove extra decrement of transaction deep level.
- Fixes: #4566
+ Fixes #4566.
*Paul Nikitochkin*
* Reset @column_defaults when assigning `locking_column`.
We had a potential problem. For example:
- class Post < ActiveRecord::Base
- self.column_defaults # if we call this unintentionally before setting locking_column ...
- self.locking_column = 'my_locking_column'
- end
+ class Post < ActiveRecord::Base
+ self.column_defaults # if we call this unintentionally before setting locking_column ...
+ self.locking_column = 'my_locking_column'
+ end
- Post.column_defaults["my_locking_column"]
- => nil # expected value is 0 !
+ Post.column_defaults["my_locking_column"]
+ => nil # expected value is 0 !
*kennyj*
* Remove extra select and update queries on save/touch/destroy ActiveRecord model
with belongs to reflection with option `touch: true`.
- Fixes: #11288
+ Fixes #11288.
*Paul Nikitochkin*
@@ -417,7 +697,7 @@
*Neeraj Singh*
-* Fixture setup does no longer depend on `ActiveRecord::Base.configurations`.
+* Fixture setup no longer depends on `ActiveRecord::Base.configurations`.
This is relevant when `ENV["DATABASE_URL"]` is used in place of a `database.yml`.
*Yves Senn*
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 9d418dacaf..f19f5ecdf9 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -145,10 +145,6 @@ module ActiveRecord
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
autoload :PostgreSQLDatabaseTasks,
'active_record/tasks/postgresql_database_tasks'
-
- autoload :FirebirdDatabaseTasks, 'active_record/tasks/firebird_database_tasks'
- autoload :SqlserverDatabaseTasks, 'active_record/tasks/sqlserver_database_tasks'
- autoload :OracleDatabaseTasks, 'active_record/tasks/oracle_database_tasks'
end
autoload :TestFixtures, 'active_record/fixtures'
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index d075edc159..0d5313956b 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -224,7 +224,7 @@ module ActiveRecord
writer_method(name, class_name, mapping, allow_nil, converter)
reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self)
- Reflection.add_reflection self, part_id, reflection
+ Reflection.add_aggregate_reflection self, part_id, reflection
end
private
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 33cbafc6aa..74e2774626 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -73,12 +73,6 @@ module ActiveRecord
end
end
- class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
- def initialize(reflection)
- super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
- end
- end
-
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
def initialize(reflection)
super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
@@ -114,7 +108,6 @@ module ActiveRecord
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
- autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
@@ -1560,8 +1553,39 @@ module ActiveRecord
# has_and_belongs_to_many :categories, join_table: "prods_cats"
# has_and_belongs_to_many :categories, -> { readonly }
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
- reflection = Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension)
- Reflection.add_reflection self, name, reflection
+ if scope.is_a?(Hash)
+ options = scope
+ scope = nil
+ end
+
+ builder = Builder::HasAndBelongsToMany.new name, self, options
+
+ join_model = builder.through_model
+
+ middle_reflection = builder.middle_reflection join_model
+
+ Builder::HasMany.define_callbacks self, middle_reflection
+ Reflection.add_reflection self, middle_reflection.name, middle_reflection
+
+ include Module.new {
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def destroy_associations
+ association(:#{middle_reflection.name}).delete_all(:delete_all)
+ association(:#{name}).reset
+ super
+ end
+ RUBY
+ }
+
+ hm_options = {}
+ hm_options[:through] = middle_reflection.name
+ hm_options[:source] = join_model.right_reflection.name
+
+ [:before_add, :after_add, :before_remove, :after_remove].each do |k|
+ hm_options[k] = options[k] if options.key? k
+ end
+
+ has_many name, scope, hm_options, &extension
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 67d24b35d1..e6a45487d0 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -13,11 +13,11 @@ module ActiveRecord
# BelongsToAssociation
# BelongsToPolymorphicAssociation
# CollectionAssociation
- # HasAndBelongsToManyAssociation
# HasManyAssociation
# HasManyThroughAssociation + ThroughAssociation
class Association #:nodoc:
attr_reader :owner, :target, :reflection
+ attr_accessor :inversed
delegate :options, :to => :reflection
@@ -43,6 +43,7 @@ module ActiveRecord
@loaded = false
@target = nil
@stale_state = nil
+ @inversed = false
end
# Reloads the \target and returns +self+ on success.
@@ -60,8 +61,9 @@ module ActiveRecord
# Asserts the \target has been loaded setting the \loaded flag to +true+.
def loaded!
- @loaded = true
+ @loaded = true
@stale_state = stale_state
+ @inversed = false
end
# The target is stale if the target no longer points to the record(s) that the
@@ -71,7 +73,7 @@ module ActiveRecord
#
# Note that if the target has not been loaded, it is not considered stale.
def stale_target?
- loaded? && @stale_state != stale_state
+ !inversed && loaded? && @stale_state != stale_state
end
# Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
@@ -105,6 +107,7 @@ module ActiveRecord
if record && invertible_for?(record)
inverse = record.association(inverse_reflection_for(record).name)
inverse.target = owner
+ inverse.inversed = true
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 8027acfb83..17f056e764 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -44,18 +44,6 @@ module ActiveRecord
chain.each_with_index do |reflection, i|
table, foreign_table = tables.shift, tables.first
- if reflection.source_macro == :has_and_belongs_to_many
- join_table = tables.shift
-
- scope = scope.joins(join(
- join_table,
- table[reflection.association_primary_key].
- eq(join_table[reflection.association_foreign_key])
- ))
-
- table, foreign_table = join_table, tables.first
- end
-
if reflection.source_macro == :belongs_to
if reflection.options[:polymorphic]
key = reflection.association_primary_key(self.klass)
@@ -90,7 +78,8 @@ module ActiveRecord
scope = scope.joins(join(foreign_table, constraint))
end
- klass = i == 0 ? self.klass : reflection.klass
+ is_first_chain = i == 0
+ klass = is_first_chain ? self.klass : reflection.klass
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
@@ -101,7 +90,10 @@ module ActiveRecord
scope.merge! item.except(:where, :includes, :bind)
end
- scope.includes! item.includes_values
+ if is_first_chain
+ scope.includes! item.includes_values
+ end
+
scope.where_values += item.where_values
scope.order_values |= item.order_values
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 34de1a1f32..d8d68eb908 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -8,7 +8,6 @@
# - HasOneAssociation
# - CollectionAssociation
# - HasManyAssociation
-# - HasAndBelongsToManyAssociation
module ActiveRecord::Associations::Builder
class Association #:nodoc:
@@ -17,11 +16,17 @@ module ActiveRecord::Associations::Builder
end
self.extensions = []
- VALID_OPTIONS = [:class_name, :foreign_key, :validate]
-
- attr_reader :name, :scope, :options
+ VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate]
def self.build(model, name, scope, options, &block)
+ extension = define_extensions model, name, &block
+ reflection = create_reflection model, name, scope, options, extension
+ define_accessors model, reflection
+ define_callbacks model, reflection
+ reflection
+ end
+
+ def self.create_reflection(model, name, scope, options, extension = nil)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
if scope.is_a?(Hash)
@@ -29,47 +34,48 @@ module ActiveRecord::Associations::Builder
scope = nil
end
- builder = new(name, scope, options, &block)
- reflection = builder.build(model)
- builder.define_accessors model
- builder.define_callbacks model, reflection
- builder.define_extensions model
- reflection
- end
+ validate_options(options)
- def initialize(name, scope, options)
- @name = name
- @scope = scope
- @options = options
+ scope = build_scope(scope, extension)
- validate_options
+ ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ end
+
+ def self.build_scope(scope, extension)
+ new_scope = scope
if scope && scope.arity == 0
- @scope = proc { instance_exec(&scope) }
+ new_scope = proc { instance_exec(&scope) }
+ end
+
+ if extension
+ new_scope = wrap_scope new_scope, extension
end
+
+ new_scope
end
- def build(model)
- ActiveRecord::Reflection.create(macro, name, scope, options, model)
+ def self.wrap_scope(scope, extension)
+ scope
end
- def macro
+ def self.macro
raise NotImplementedError
end
- def valid_options
+ def self.valid_options(options)
VALID_OPTIONS + Association.extensions.flat_map(&:valid_options)
end
- def validate_options
- options.assert_valid_keys(valid_options)
+ def self.validate_options(options)
+ options.assert_valid_keys(valid_options(options))
end
- def define_extensions(model)
+ def self.define_extensions(model, name)
end
- def define_callbacks(model, reflection)
- add_before_destroy_callbacks(model, name) if options[:dependent]
+ def self.define_callbacks(model, reflection)
+ add_before_destroy_callbacks(model, reflection) if reflection.options[:dependent]
Association.extensions.each do |extension|
extension.build model, reflection
end
@@ -81,14 +87,14 @@ module ActiveRecord::Associations::Builder
# end
#
# Post.first.comments and Post.first.comments= methods are defined by this method...
-
- def define_accessors(model)
+ def self.define_accessors(model, reflection)
mixin = model.generated_feature_methods
- define_readers(mixin)
- define_writers(mixin)
+ name = reflection.name
+ define_readers(mixin, name)
+ define_writers(mixin, name)
end
- def define_readers(mixin)
+ def self.define_readers(mixin, name)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}(*args)
association(:#{name}).reader(*args)
@@ -96,7 +102,7 @@ module ActiveRecord::Associations::Builder
CODE
end
- def define_writers(mixin)
+ def self.define_writers(mixin, name)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}=(value)
association(:#{name}).writer(value)
@@ -104,17 +110,16 @@ module ActiveRecord::Associations::Builder
CODE
end
- def valid_dependent_options
+ def self.valid_dependent_options
raise NotImplementedError
end
- private
-
- def add_before_destroy_callbacks(model, name)
- unless valid_dependent_options.include? options[:dependent]
- raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{options[:dependent]}"
+ def self.add_before_destroy_callbacks(model, reflection)
+ unless valid_dependent_options.include? reflection.options[:dependent]
+ raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{reflection.options[:dependent]}"
end
+ name = reflection.name
model.before_destroy lambda { |o| o.association(name).handle_dependency }
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 4e88b50ec5..aa43c34d86 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -1,50 +1,44 @@
module ActiveRecord::Associations::Builder
class BelongsTo < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:belongs_to
end
- def valid_options
+ def self.valid_options(options)
super + [:foreign_type, :polymorphic, :touch]
end
- def constructable?
- !options[:polymorphic]
- end
-
- def valid_dependent_options
+ def self.valid_dependent_options
[:destroy, :delete]
end
- def define_callbacks(model, reflection)
+ def self.define_callbacks(model, reflection)
super
- add_counter_cache_callbacks(model, reflection) if options[:counter_cache]
- add_touch_callbacks(model, reflection) if options[:touch]
+ add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
end
- def define_accessors(mixin)
+ def self.define_accessors(mixin, reflection)
super
add_counter_cache_methods mixin
end
- private
-
- def add_counter_cache_methods(mixin)
+ def self.add_counter_cache_methods(mixin)
return if mixin.method_defined? :belongs_to_counter_cache_after_create
mixin.class_eval do
- def belongs_to_counter_cache_after_create(association, reflection)
- if record = send(association.name)
+ def belongs_to_counter_cache_after_create(reflection)
+ if record = send(reflection.name)
cache_column = reflection.counter_cache_column
record.class.increment_counter(cache_column, record.id)
@_after_create_counter_called = true
end
end
- def belongs_to_counter_cache_before_destroy(association, reflection)
+ def belongs_to_counter_cache_before_destroy(reflection)
foreign_key = reflection.foreign_key.to_sym
unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
- record = send association.name
+ record = send reflection.name
if record && !self.destroyed?
cache_column = reflection.counter_cache_column
record.class.decrement_counter(cache_column, record.id)
@@ -52,13 +46,13 @@ module ActiveRecord::Associations::Builder
end
end
- def belongs_to_counter_cache_after_update(association, reflection)
+ def belongs_to_counter_cache_after_update(reflection)
foreign_key = reflection.foreign_key
cache_column = reflection.counter_cache_column
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
- elsif attribute_changed?(foreign_key) && !new_record? && association.constructable?
+ elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
model = reflection.klass
foreign_key_was = attribute_was foreign_key
foreign_key = attribute foreign_key
@@ -74,20 +68,19 @@ module ActiveRecord::Associations::Builder
end
end
- def add_counter_cache_callbacks(model, reflection)
+ def self.add_counter_cache_callbacks(model, reflection)
cache_column = reflection.counter_cache_column
- association = self
model.after_create lambda { |record|
- record.belongs_to_counter_cache_after_create(association, reflection)
+ record.belongs_to_counter_cache_after_create(reflection)
}
model.before_destroy lambda { |record|
- record.belongs_to_counter_cache_before_destroy(association, reflection)
+ record.belongs_to_counter_cache_before_destroy(reflection)
}
model.after_update lambda { |record|
- record.belongs_to_counter_cache_after_update(association, reflection)
+ record.belongs_to_counter_cache_after_update(reflection)
}
klass = reflection.class_name.safe_constantize
@@ -120,10 +113,10 @@ module ActiveRecord::Associations::Builder
end
end
- def add_touch_callbacks(model, reflection)
+ def self.add_touch_callbacks(model, reflection)
foreign_key = reflection.foreign_key
- n = name
- touch = options[:touch]
+ n = reflection.name
+ touch = reflection.options[:touch]
callback = lambda { |record|
BelongsTo.touch_record(record, foreign_key, n, touch)
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 7bd0687c0b..2ff67f904d 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,4 +1,4 @@
-# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
+# This class is inherited by the has_many and has_many_and_belongs_to_many association classes
require 'active_record/associations'
@@ -7,35 +7,29 @@ module ActiveRecord::Associations::Builder
CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove]
- def valid_options
+ def self.valid_options(options)
super + [:table_name, :before_add,
:after_add, :before_remove, :after_remove, :extend]
end
- attr_reader :block_extension
-
- def initialize(name, scope, options)
+ def self.define_callbacks(model, reflection)
super
- @mod = nil
- if block_given?
- @mod = Module.new(&Proc.new)
- @scope = wrap_scope @scope, @mod
- end
+ name = reflection.name
+ options = reflection.options
+ CALLBACKS.each { |callback_name|
+ define_callback(model, callback_name, name, options)
+ }
end
- def define_callbacks(model, reflection)
- super
- CALLBACKS.each { |callback_name| define_callback(model, callback_name) }
- end
-
- def define_extensions(model)
- if @mod
+ def self.define_extensions(model, name)
+ if block_given?
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- model.parent.const_set(extension_module_name, @mod)
+ extension = Module.new(&Proc.new)
+ model.parent.const_set(extension_module_name, extension)
end
end
- def define_callback(model, callback_name)
+ def self.define_callback(model, callback_name, name, options)
full_callback_name = "#{callback_name}_for_#{name}"
# TODO : why do i need method_defined? I think its because of the inheritance chain
@@ -54,8 +48,7 @@ module ActiveRecord::Associations::Builder
end
# Defines the setter and getter methods for the collection_singular_ids.
-
- def define_readers(mixin)
+ def self.define_readers(mixin, name)
super
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
@@ -65,7 +58,7 @@ module ActiveRecord::Associations::Builder
CODE
end
- def define_writers(mixin)
+ def self.define_writers(mixin, name)
super
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
@@ -75,9 +68,7 @@ module ActiveRecord::Associations::Builder
CODE
end
- private
-
- def wrap_scope(scope, mod)
+ def self.wrap_scope(scope, mod)
if scope
proc { |owner| instance_exec(owner, &scope).extending(mod) }
else
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 55ec3bf4f1..1c9c04b044 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -1,24 +1,121 @@
module ActiveRecord::Associations::Builder
- class HasAndBelongsToMany < CollectionAssociation #:nodoc:
- def macro
- :has_and_belongs_to_many
+ class HasAndBelongsToMany # :nodoc:
+ class JoinTableResolver
+ KnownTable = Struct.new :join_table
+
+ class KnownClass
+ def initialize(lhs_class, rhs_class_name)
+ @lhs_class = lhs_class
+ @rhs_class_name = rhs_class_name
+ @join_table = nil
+ end
+
+ def join_table
+ @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
+ end
+
+ private
+ def klass; @rhs_class_name.constantize; end
+ end
+
+ def self.build(lhs_class, name, options)
+ if options[:join_table]
+ KnownTable.new options[:join_table]
+ else
+ class_name = options.fetch(:class_name) {
+ name.to_s.camelize.singularize
+ }
+ KnownClass.new lhs_class, class_name
+ end
+ end
+ end
+
+ attr_reader :lhs_model, :association_name, :options
+
+ def initialize(association_name, lhs_model, options)
+ @association_name = association_name
+ @lhs_model = lhs_model
+ @options = options
+ end
+
+ def through_model
+ habtm = JoinTableResolver.build lhs_model, association_name, options
+
+ join_model = Class.new(ActiveRecord::Base) {
+ class << self;
+ attr_accessor :class_resolver
+ attr_accessor :name
+ attr_accessor :table_name_resolver
+ attr_accessor :left_reflection
+ attr_accessor :right_reflection
+ end
+
+ def self.table_name
+ table_name_resolver.join_table
+ end
+
+ def self.compute_type(class_name)
+ class_resolver.compute_type class_name
+ end
+
+ def self.add_left_association(name, options)
+ belongs_to name, options
+ self.left_reflection = reflect_on_association(name)
+ end
+
+ def self.add_right_association(name, options)
+ rhs_name = name.to_s.singularize.to_sym
+ belongs_to rhs_name, options
+ self.right_reflection = reflect_on_association(rhs_name)
+ end
+
+ }
+
+ join_model.name = "HABTM_#{association_name.to_s.camelize}"
+ join_model.table_name_resolver = habtm
+ join_model.class_resolver = lhs_model
+
+ join_model.add_left_association :left_side, class: lhs_model
+ join_model.add_right_association association_name, belongs_to_options(options)
+ join_model
+ end
+
+ def middle_reflection(join_model)
+ middle_name = [lhs_model.name.downcase.pluralize,
+ association_name].join('_').gsub(/::/, '_').to_sym
+ middle_options = middle_options join_model
+
+ HasMany.create_reflection(lhs_model,
+ middle_name,
+ nil,
+ middle_options)
end
- def valid_options
- super + [:join_table, :association_foreign_key]
+ private
+
+ def middle_options(join_model)
+ middle_options = {}
+ middle_options[:class] = join_model
+ middle_options[:source] = join_model.left_reflection.name
+ if options.key? :foreign_key
+ middle_options[:foreign_key] = options[:foreign_key]
+ end
+ middle_options
end
- def define_callbacks(model, reflection)
- super
- name = self.name
- model.send(:include, Module.new {
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def destroy_associations
- association(:#{name}).delete_all
- super
- end
- RUBY
- })
+ def belongs_to_options(options)
+ rhs_options = {}
+
+ if options.key? :class_name
+ rhs_options[:foreign_key] = options[:class_name].foreign_key
+ rhs_options[:class_name] = options[:class_name]
+ end
+
+ if options.key? :association_foreign_key
+ rhs_options[:foreign_key] = options[:association_foreign_key]
+ end
+
+ rhs_options
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index a60cb4769a..227184cd19 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -1,14 +1,14 @@
module ActiveRecord::Associations::Builder
class HasMany < CollectionAssociation #:nodoc:
- def macro
+ def self.macro
:has_many
end
- def valid_options
+ def self.valid_options(options)
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
end
- def valid_dependent_options
+ def self.valid_dependent_options
[:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 62d454ce55..064a3c8b51 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -1,27 +1,21 @@
module ActiveRecord::Associations::Builder
class HasOne < SingularAssociation #:nodoc:
- def macro
+ def self.macro
:has_one
end
- def valid_options
+ def self.valid_options(options)
valid = super + [:order, :as]
valid += [:through, :source, :source_type] if options[:through]
valid
end
- def constructable?
- !options[:through]
- end
-
- def valid_dependent_options
+ def self.valid_dependent_options
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
end
- private
-
- def add_before_destroy_callbacks(model, name)
- super unless options[:through]
+ def self.add_before_destroy_callbacks(model, reflection)
+ super unless reflection.options[:through]
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index d97c0e9afd..2a4b1c441f 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -1,23 +1,18 @@
-# This class is inherited by the has_one and belongs_to association classes
+# This class is inherited by the has_one and belongs_to association classes
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
- def valid_options
+ def self.valid_options(options)
super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
end
- def constructable?
- true
- end
-
- def define_accessors(model)
+ def self.define_accessors(model, reflection)
super
- define_constructors(model.generated_feature_methods) if constructable?
+ define_constructors(model.generated_feature_methods, reflection.name) if reflection.constructable?
end
# Defines the (build|create)_association methods for belongs_to or has_one association
-
- def define_constructors(mixin)
+ def self.define_constructors(mixin, name)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def build_#{name}(*args, &block)
association(:#{name}).build(*args, &block)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 228c500f0a..6b06a5f7fd 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -7,7 +7,6 @@ module ActiveRecord
# collections. See the class hierarchy in AssociationProxy.
#
# CollectionAssociation:
- # HasAndBelongsToManyAssociation => has_and_belongs_to_many
# HasManyAssociation => has_many
# HasManyThroughAssociation + ThroughAssociation => has_many :through
#
@@ -80,14 +79,13 @@ module ActiveRecord
load_target.find(*args) { |*block_args| yield(*block_args) }
else
if options[:inverse_of] && loaded?
- args = args.flatten
- raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank?
-
+ args_flatten = args.flatten
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
result = find_by_scan(*args)
result_size = Array(result).size
- if !result || result_size != args.size
- scope.raise_record_not_found_exception!(args, result_size, args.size)
+ if !result || result_size != args_flatten.size
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
else
result
end
@@ -197,9 +195,7 @@ module ActiveRecord
# Count all records using SQL. Construct options and pass them with
# scope to the target class's +count+.
- def count(column_name = nil, count_options = {})
- column_name, count_options = nil, column_name if column_name.is_a?(Hash)
-
+ def count(column_name = nil)
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -528,21 +524,20 @@ module ActiveRecord
# * target already loaded
# * owner is new record
# * target contains new or changed record(s)
- # * the first arg is an integer (which indicates the number of records to be returned)
def fetch_first_or_last_using_find?(args)
if args.first.is_a?(Hash)
true
else
!(loaded? ||
owner.new_record? ||
- target.any? { |record| record.new_record? || record.changed? } ||
- args.first.kind_of?(Integer))
+ target.any? { |record| record.new_record? || record.changed? })
end
end
def include_in_memory?(record)
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
- owner.send(reflection.through_reflection.name).any? { |source|
+ assoc = owner.association(reflection.through_reflection.name)
+ assoc.reader.any? { |source|
target = source.send(reflection.source_reflection.name)
target.respond_to?(:include?) ? target.include?(record) : target == record
} || target.include?(record)
@@ -555,14 +550,14 @@ module ActiveRecord
# specified, then #find scans the entire collection.
def find_by_scan(*args)
expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
+ ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
if ids.size == 1
id = ids.first
- record = load_target.detect { |r| id == r.id }
+ record = load_target.detect { |r| id == r.id.to_s }
expects_array ? [ record ] : record
else
- load_target.select { |r| ids.include?(r.id) }
+ load_target.select { |r| ids.include?(r.id.to_s) }
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index ea7f768a68..2e70a07962 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -281,7 +281,7 @@ module ActiveRecord
# so method calls may be chained.
#
# class Person < ActiveRecord::Base
- # pets :has_many
+ # has_many :pets
# end
#
# person.pets.size # => 0
@@ -669,8 +669,8 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil, options = {})
- @association.count(column_name, options)
+ def count(column_name = nil)
+ @association.count(column_name)
end
# Returns the size of the collection. If the collection hasn't been loaded,
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
deleted file mode 100644
index b2e6c708bf..0000000000
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module ActiveRecord
- # = Active Record Has And Belongs To Many Association
- module Associations
- class HasAndBelongsToManyAssociation < CollectionAssociation #:nodoc:
- attr_reader :join_table
-
- def initialize(owner, reflection)
- @join_table = Arel::Table.new(reflection.join_table)
- super
- end
-
- def insert_record(record, validate = true, raise = false)
- if record.new_record?
- if raise
- record.save!(:validate => validate)
- else
- return unless record.save(:validate => validate)
- end
- end
-
- stmt = join_table.compile_insert(
- join_table[reflection.foreign_key] => owner.id,
- join_table[reflection.association_foreign_key] => record.id
- )
-
- owner.class.connection.insert stmt
-
- record
- end
-
- private
-
- def count_records
- load_target.size
- end
-
- def delete_records(records, method)
- relation = join_table
- condition = relation[reflection.foreign_key].eq(owner.id)
-
- unless records == :all
- condition = condition.and(
- relation[reflection.association_foreign_key]
- .in(records.map { |x| x.id }.compact)
- )
- end
-
- owner.class.connection.delete(relation.where(condition).compile_delete)
- end
-
- def invertible_for?(record)
- false
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 607ed0da46..0a23109b9b 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -32,6 +32,7 @@ module ActiveRecord
def insert_record(record, validate = true, raise = false)
set_owner_attributes(record)
+ set_inverse_instance(record)
if raise
record.save!(:validate => validate)
@@ -125,7 +126,11 @@ module ActiveRecord
end
def foreign_key_present?
- owner.attribute_present?(reflection.association_primary_key)
+ if reflection.klass.primary_key
+ owner.attribute_present?(reflection.association_primary_key)
+ else
+ false
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index a74dd1cdab..56331bbb0b 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -140,7 +140,21 @@ module ActiveRecord
case method
when :destroy
- count = scope.destroy_all.length
+ if scope.klass.primary_key
+ count = scope.destroy_all.length
+ else
+ scope.to_a.each do |record|
+ record.run_callbacks :destroy
+ end
+
+ arel = scope.arel
+
+ stmt = Arel::DeleteManager.new arel.engine
+ stmt.from scope.klass.arel_table
+ stmt.wheres = arel.constraints
+
+ count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values)
+ end
when :nullify
count = scope.update_all(source_reflection.foreign_key => nil)
else
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 3ab1ea1ff4..0008600418 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -27,6 +27,8 @@ module ActiveRecord
return self.target if !(target || record)
if (target != record) || record.changed?
+ save &&= owner.persisted?
+
transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
@@ -34,7 +36,7 @@ module ActiveRecord
set_owner_attributes(record)
set_inverse_instance(record)
- if owner.persisted? && save && !record.save
+ if save && !record.save
nullify_owner_attributes(record)
set_owner_attributes(target) if target
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 5aa17e5fbb..c3ac0680ea 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -1,11 +1,75 @@
module ActiveRecord
module Associations
class JoinDependency # :nodoc:
- autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
- attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
+ class Aliases # :nodoc:
+ def initialize(tables)
+ @tables = tables
+ @alias_cache = tables.each_with_object({}) { |table,h|
+ h[table.node] = table.columns.each_with_object({}) { |column,i|
+ i[column.name] = column.alias
+ }
+ }
+ @name_and_alias_cache = tables.each_with_object({}) { |table,h|
+ h[table.node] = table.columns.map { |column|
+ [column.name, column.alias]
+ }
+ }
+ end
+
+ def columns
+ @tables.flat_map { |t| t.column_aliases }
+ end
+
+ # An array of [column_name, alias] pairs for the table
+ def column_aliases(node)
+ @name_and_alias_cache[node]
+ end
+
+ def column_alias(node, column)
+ @alias_cache[node][column]
+ end
+
+ class Table < Struct.new(:node, :columns)
+ def table
+ Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
+ end
+
+ def column_aliases
+ t = table
+ columns.map { |column| t[column.name].as Arel.sql column.alias }
+ end
+ end
+ Column = Struct.new(:name, :alias)
+ end
+
+ attr_reader :alias_tracker, :base_klass, :join_root
+
+ def self.make_tree(associations)
+ hash = {}
+ walk_tree associations, hash
+ hash
+ end
+
+ def self.walk_tree(associations, hash)
+ case associations
+ when Symbol, String
+ hash[associations.to_sym] ||= {}
+ when Array
+ associations.each do |assoc|
+ walk_tree assoc, hash
+ end
+ when Hash
+ associations.each do |k,v|
+ cache = hash[k] ||= {}
+ walk_tree v, cache
+ end
+ else
+ raise ConfigurationError, associations.inspect
+ end
+ end
# base is the base class on which operation is taking place.
# associations is the list of associations which are joined using hash, symbol or array.
@@ -29,211 +93,181 @@ module ActiveRecord
# joins #=> []
#
def initialize(base, associations, joins)
- @base_klass = base
- @table_joins = joins
- @join_parts = [JoinBase.new(base)]
- @associations = {}
- @reflections = []
@alias_tracker = AliasTracker.new(base.connection, joins)
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
- build(associations)
+ tree = self.class.make_tree associations
+ @join_root = JoinBase.new base, build(tree, base)
+ @join_root.children.each { |child| construct_tables! @join_root, child }
end
- def graft(*associations)
- associations.each do |association|
- join_associations.detect {|a| association == a} ||
- build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
- end
- self
+ def reflections
+ join_root.drop(1).map!(&:reflection)
end
- def join_associations
- join_parts.drop 1
- end
+ def join_constraints(outer_joins)
+ joins = join_root.children.flat_map { |child|
+ make_inner_joins join_root, child
+ }
- def join_base
- join_parts.first
+ joins.concat outer_joins.flat_map { |oj|
+ if join_root.match? oj.join_root
+ walk join_root, oj.join_root
+ else
+ oj.join_root.children.flat_map { |child|
+ make_outer_joins join_root, child
+ }
+ end
+ }
end
- def join_relation(relation)
- join_associations.inject(relation) do |rel,association|
- association.join_relation(rel)
- end
+ def aliases
+ Aliases.new join_root.each_with_index.map { |join_part,i|
+ columns = join_part.column_names.each_with_index.map { |column_name,j|
+ Aliases::Column.new column_name, "t#{i}_r#{j}"
+ }
+ Aliases::Table.new(join_part, columns)
+ }
end
- def columns
- join_parts.collect { |join_part|
- table = join_part.aliased_table
- join_part.column_names_with_alias.collect{ |column_name, aliased_name|
- table[column_name].as Arel.sql(aliased_name)
+ def instantiate(result_set, aliases)
+ primary_key = aliases.column_alias(join_root, join_root.primary_key)
+ type_caster = result_set.column_type primary_key
+
+ seen = Hash.new { |h,parent_klass|
+ h[parent_klass] = Hash.new { |i,parent_id|
+ i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
}
- }.flatten
- end
+ }
- def instantiate(rows)
- primary_key = join_base.aliased_primary_key
- parents = {}
+ model_cache = Hash.new { |h,klass| h[klass] = {} }
+ parents = model_cache[join_root]
+ column_aliases = aliases.column_aliases join_root
- records = rows.map { |model|
- primary_id = model[primary_key]
- parent = parents[primary_id] ||= join_base.instantiate(model)
- construct(parent, @associations, join_associations, model)
- parent
- }.uniq
+ result_set.each { |row_hash|
+ primary_id = type_caster.type_cast row_hash[primary_key]
+ parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
+ }
- remove_duplicate_results!(base_klass, records, @associations)
- records
+ parents.values
end
- protected
-
- def remove_duplicate_results!(base, records, associations)
- case associations
- when Symbol, String
- reflection = base.reflections[associations]
- remove_uniq_by_reflection(reflection, records)
- when Array
- associations.each do |association|
- remove_duplicate_results!(base, records, association)
- end
- when Hash
- associations.each_key do |name|
- reflection = base.reflections[name]
- remove_uniq_by_reflection(reflection, records)
-
- parent_records = []
- records.each do |record|
- if descendant = record.send(reflection.name)
- if reflection.collection?
- parent_records.concat descendant.target.uniq
- else
- parent_records << descendant
- end
- end
- end
+ private
- remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
- end
- end
+ def make_constraints(parent, child, tables, join_type)
+ chain = child.reflection.chain
+ foreign_table = parent.table
+ foreign_klass = parent.base_klass
+ child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
end
- def cache_joined_association(association)
- associations = []
- parent = association.parent
- while parent != join_base
- associations.unshift(parent.reflection.name)
- parent = parent.parent
- end
- ref = @associations
- associations.each do |key|
- ref = ref[key]
- end
- ref[association.reflection.name] ||= {}
- end
+ def make_outer_joins(parent, child)
+ tables = table_aliases_for(parent, child)
+ join_type = Arel::OuterJoin
+ joins = make_constraints parent, child, tables, join_type
- def build(associations, parent = join_parts.last, join_type = Arel::InnerJoin)
- case associations
- when Symbol, String
- reflection = parent.reflections[associations.intern] or
- raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
- unless join_association = find_join_association(reflection, parent)
- @reflections << reflection
- join_association = build_join_association(reflection, parent)
- join_association.join_type = join_type
- @join_parts << join_association
- cache_joined_association(join_association)
- end
- join_association
- when Array
- associations.each do |association|
- build(association, parent, join_type)
- end
- when Hash
- associations.keys.sort_by { |a| a.to_s }.each do |name|
- join_association = build(name, parent, join_type)
- build(associations[name], join_association, join_type)
- end
- else
- raise ConfigurationError, associations.inspect
- end
+ joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
end
- def find_join_association(name_or_reflection, parent)
- if String === name_or_reflection
- name_or_reflection = name_or_reflection.to_sym
- end
+ def make_inner_joins(parent, child)
+ tables = child.tables
+ join_type = Arel::InnerJoin
+ joins = make_constraints parent, child, tables, join_type
- join_associations.detect { |j|
- j.reflection == name_or_reflection && j.parent == parent
+ joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
+ end
+
+ def table_aliases_for(parent, node)
+ node.reflection.chain.map { |reflection|
+ alias_tracker.aliased_table_for(
+ reflection.table_name,
+ table_alias_for(reflection, parent, reflection != node.reflection)
+ )
}
end
- def remove_uniq_by_reflection(reflection, records)
- if reflection && reflection.collection?
- records.each { |record| record.send(reflection.name).target.uniq! }
- end
+ def construct_tables!(parent, node)
+ node.tables = table_aliases_for(parent, node)
+ node.children.each { |child| construct_tables! node, child }
end
- def build_join_association(reflection, parent)
- JoinAssociation.new(reflection, self, parent)
+ def table_alias_for(reflection, parent, join)
+ name = "#{reflection.plural_name}_#{parent.table_name}"
+ name << "_join" if join
+ name
end
- def construct(parent, associations, join_parts, row)
- case associations
- when Symbol, String
- name = associations.to_s
+ def walk(left, right)
+ intersection, missing = right.children.map { |node1|
+ [left.children.find { |node2| node1.match? node2 }, node1]
+ }.partition(&:first)
- join_part = join_parts.detect { |j|
- j.reflection.name.to_s == name &&
- j.parent_table_name == parent.class.table_name }
+ ojs = missing.flat_map { |_,n| make_outer_joins left, n }
+ intersection.flat_map { |l,r| walk l, r }.concat ojs
+ end
- raise(ConfigurationError, "No such association") unless join_part
+ def find_reflection(klass, name)
+ klass.reflect_on_association(name) or
+ raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
+ end
- join_parts.delete(join_part)
- construct_association(parent, join_part, row)
- when Array
- associations.each do |association|
- construct(parent, association, join_parts, row)
- end
- when Hash
- associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
- association = construct(parent, association_name, join_parts, row)
- construct(association, assoc, join_parts, row) if association
+ def build(associations, base_klass)
+ associations.map do |name, right|
+ reflection = find_reflection base_klass, name
+ reflection.check_validity!
+
+ if reflection.options[:polymorphic]
+ raise EagerLoadPolymorphicError.new(reflection)
end
- else
- raise ConfigurationError, associations.inspect
+
+ JoinAssociation.new reflection, build(right, reflection.klass)
end
end
- def construct_association(record, join_part, row)
- return if record.id.to_s != join_part.parent.record_id(row).to_s
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
+ primary_id = ar_parent.id
- macro = join_part.reflection.macro
- if macro == :has_one
- return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
- association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
- set_target_and_inverse(join_part, association, record)
- else
- association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
- case macro
- when :has_many, :has_and_belongs_to_many
- other = record.association(join_part.reflection.name)
+ parent.children.each do |node|
+ if node.reflection.collection?
+ other = ar_parent.association(node.reflection.name)
other.loaded!
- other.target.push(association) if association
- other.set_inverse_instance(association)
- when :belongs_to
- set_target_and_inverse(join_part, association, record)
else
- raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
+ if ar_parent.association_cache.key?(node.reflection.name)
+ model = ar_parent.association(node.reflection.name).target
+ construct(model, node, row, rs, seen, model_cache, aliases)
+ next
+ end
+ end
+
+ key = aliases.column_alias(node, node.primary_key)
+ id = row[key]
+ next if id.nil?
+
+ model = seen[parent.base_klass][primary_id][node.base_klass][id]
+
+ if model
+ construct(model, node, row, rs, seen, model_cache, aliases)
+ else
+ model = construct_model(ar_parent, node, row, model_cache, id, aliases)
+ seen[parent.base_klass][primary_id][node.base_klass][id] = model
+ construct(model, node, row, rs, seen, model_cache, aliases)
end
end
- association
end
- def set_target_and_inverse(join_part, association, record)
- other = record.association(join_part.reflection.name)
- other.target = association
- other.set_inverse_instance(association)
+ def construct_model(record, node, row, model_cache, id, aliases)
+ model = model_cache[node][id] ||= node.instantiate(row,
+ aliases.column_aliases(node))
+ other = record.association(node.reflection.name)
+
+ if node.reflection.collection?
+ other.target.push(model)
+ else
+ other.target = model
+ end
+
+ other.set_inverse_instance(model)
+ model
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 58fc00d811..191d430636 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -1,77 +1,31 @@
+require 'active_record/associations/join_dependency/join_part'
+
module ActiveRecord
module Associations
class JoinDependency # :nodoc:
class JoinAssociation < JoinPart # :nodoc:
- include JoinHelper
-
# The reflection of the association represented
attr_reader :reflection
- # The JoinDependency object which this JoinAssociation exists within. This is mainly
- # relevant for generating aliases which do not conflict with other joins which are
- # part of the query.
- attr_reader :join_dependency
-
- # A JoinBase instance representing the active record we are joining onto.
- # (So in Author.has_many :posts, the Author would be that base record.)
- attr_reader :parent
-
- # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
- attr_accessor :join_type
-
- # These implement abstract methods from the superclass
- attr_reader :aliased_prefix
-
- attr_reader :tables
+ attr_accessor :tables
- delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
- delegate :alias_tracker, :to => :join_dependency
-
- def initialize(reflection, join_dependency, parent = nil)
- reflection.check_validity!
-
- if reflection.options[:polymorphic]
- raise EagerLoadPolymorphicError.new(reflection)
- end
-
- super(reflection.klass)
+ def initialize(reflection, children)
+ super(reflection.klass, children)
@reflection = reflection
- @join_dependency = join_dependency
- @parent = parent
- @join_type = Arel::InnerJoin
- @aliased_prefix = "t#{ join_dependency.join_parts.size }"
- @tables = construct_tables.reverse
- end
-
- def parent_table_name; parent.table_name; end
- alias :alias_suffix :parent_table_name
-
- def ==(other)
- other.class == self.class &&
- other.reflection == reflection &&
- other.parent == parent
+ @tables = nil
end
- def find_parent_in(other_join_dependency)
- other_join_dependency.join_parts.detect do |join_part|
- case parent
- when JoinBase
- parent.base_klass == join_part.base_klass
- else
- parent == join_part
- end
- end
+ def match?(other)
+ return true if self == other
+ super && reflection == other.reflection
end
- def join_constraints
+ def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
joins = []
- tables = @tables.dup
-
- foreign_table = parent.table
- foreign_klass = parent.base_klass
+ tables = tables.reverse
- scope_chain_iter = reflection.scope_chain.reverse_each
+ scope_chain_iter = scope_chain.reverse_each
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@@ -83,17 +37,6 @@ module ActiveRecord
when :belongs_to
key = reflection.association_primary_key
foreign_key = reflection.foreign_key
- when :has_and_belongs_to_many
- # Join the join table first...
- joins << join(
- table,
- table[reflection.foreign_key].
- eq(foreign_table[reflection.active_record_primary_key]))
-
- foreign_table, table = table, tables.shift
-
- key = reflection.association_primary_key
- foreign_key = reflection.association_foreign_key
else
key = reflection.foreign_key
foreign_key = reflection.active_record_primary_key
@@ -105,7 +48,7 @@ module ActiveRecord
if item.is_a?(Relation)
item
else
- ActiveRecord::Relation.create(klass, table).instance_exec(self, &item)
+ ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
end
end
@@ -125,7 +68,7 @@ module ActiveRecord
constraint = constraint.and rel.arel.constraints
end
- joins << join(table, constraint)
+ joins << table.create_join(table, table.create_on(constraint), join_type)
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, klass
@@ -162,13 +105,8 @@ module ActiveRecord
constraint
end
- def join_relation(joining_relation)
- self.join_type = Arel::OuterJoin
- joining_relation.joins(self)
- end
-
def table
- tables.last
+ tables.first
end
def aliased_table_name
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index a7dacdbfd6..3a26c25737 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -1,18 +1,16 @@
+require 'active_record/associations/join_dependency/join_part'
+
module ActiveRecord
module Associations
class JoinDependency # :nodoc:
class JoinBase < JoinPart # :nodoc:
- def ==(other)
- other.class == self.class &&
- other.base_klass == base_klass
- end
-
- def aliased_prefix
- "t0"
+ def match?(other)
+ return true if self == other
+ super && base_klass == other.base_klass
end
def table
- Arel::Table.new(table_name, arel_engine)
+ base_klass.arel_table
end
def aliased_table_name
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index b534569063..91e1c6a9d7 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -8,34 +8,36 @@ module ActiveRecord
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
# two; one for the join table and one for the target table).
class JoinPart # :nodoc:
+ include Enumerable
+
# The Active Record class which this join part is associated 'about'; for a JoinBase
# this is the actual base model, for a JoinAssociation this is the target model of the
# association.
- attr_reader :base_klass
+ attr_reader :base_klass, :children
- delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
+ delegate :table_name, :column_names, :primary_key, :to => :base_klass
- def initialize(base_klass)
+ def initialize(base_klass, children)
@base_klass = base_klass
- @cached_record = {}
@column_names_with_alias = nil
+ @children = children
end
- def aliased_table
- Arel::Nodes::TableAlias.new table, aliased_table_name
+ def name
+ reflection.name
end
- def ==(other)
- raise NotImplementedError
+ def match?(other)
+ self.class == other.class
end
- # An Arel::Table for the active_record
- def table
- raise NotImplementedError
+ def each(&block)
+ yield self
+ children.each { |child| child.each(&block) }
end
- # The prefix to be used when aliasing columns in the active_record's table
- def aliased_prefix
+ # An Arel::Table for the active_record
+ def table
raise NotImplementedError
end
@@ -44,33 +46,25 @@ module ActiveRecord
raise NotImplementedError
end
- # The alias for the primary key of the active_record's table
- def aliased_primary_key
- "#{aliased_prefix}_r0"
- end
+ def extract_record(row, column_names_with_alias)
+ # This code is performance critical as it is called per row.
+ # see: https://github.com/rails/rails/pull/12185
+ hash = {}
- # An array of [column_name, alias] pairs for the table
- def column_names_with_alias
- unless @column_names_with_alias
- @column_names_with_alias = []
+ index = 0
+ length = column_names_with_alias.length
- ([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i|
- @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
- end
+ while index < length
+ column_name, alias_name = column_names_with_alias[index]
+ hash[column_name] = row[alias_name]
+ index += 1
end
- @column_names_with_alias
- end
-
- def extract_record(row)
- Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
- end
- def record_id(row)
- row[aliased_primary_key]
+ hash
end
- def instantiate(row)
- @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
+ def instantiate(row, aliases)
+ base_klass.instantiate(extract_record(row, aliases))
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
index 27b70edf1a..f345d16841 100644
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ b/activerecord/lib/active_record/associations/join_helper.rb
@@ -10,21 +10,12 @@ module ActiveRecord
private
def construct_tables
- tables = []
- chain.each do |reflection|
- tables << alias_tracker.aliased_table_for(
+ chain.map do |reflection|
+ alias_tracker.aliased_table_for(
table_name_for(reflection),
table_alias_for(reflection, reflection != self.reflection)
)
-
- if reflection.source_macro == :has_and_belongs_to_many
- tables << alias_tracker.aliased_table_for(
- reflection.source_reflection.join_table,
- table_alias_for(reflection, true)
- )
- end
end
- tables
end
def table_name_for(reflection)
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 6cc9d6c079..2393667ac8 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -42,12 +42,9 @@ module ActiveRecord
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
autoload :HasOne, 'active_record/associations/preloader/has_one'
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
- autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
end
- attr_reader :records, :associations, :preload_scope, :model
-
# Eager loads the named associations for the given Active Record record(s).
#
# In this description, 'association name' shall refer to the name passed
@@ -82,40 +79,47 @@ module ActiveRecord
# [ :books, :author ]
# { author: :avatar }
# [ :books, { author: :avatar } ]
- def initialize(records, associations, preload_scope = nil)
- @records = Array.wrap(records).compact.uniq
- @associations = Array.wrap(associations)
- @preload_scope = preload_scope || NULL_RELATION
- end
NULL_RELATION = Struct.new(:values).new({})
- def run
- unless records.empty?
- associations.each { |association| preload(association) }
+ def preload(records, associations, preload_scope = nil)
+ records = Array.wrap(records).compact.uniq
+ associations = Array.wrap(associations)
+ preload_scope = preload_scope || NULL_RELATION
+
+ if records.empty?
+ []
+ else
+ associations.flat_map { |association|
+ preloaders_on association, records, preload_scope
+ }
end
end
private
- def preload(association)
+ def preloaders_on(association, records, scope)
case association
when Hash
- preload_hash(association)
+ preloaders_for_hash(association, records, scope)
when Symbol
- preload_one(association)
+ preloaders_for_one(association, records, scope)
when String
- preload_one(association.to_sym)
+ preloaders_for_one(association.to_sym, records, scope)
else
raise ArgumentError, "#{association.inspect} was not recognised for preload"
end
end
- def preload_hash(association)
- association.each do |parent, child|
- Preloader.new(records, parent, preload_scope).run
- Preloader.new(records.map { |record| record.send(parent) }.flatten, child).run
- end
+ def preloaders_for_hash(association, records, scope)
+ parent, child = association.to_a.first # hash should only be of length 1
+
+ loaders = preloaders_for_one parent, records, scope
+
+ recs = loaders.flat_map(&:preloaded_records).uniq
+ loaders.concat Array.wrap(child).flat_map { |assoc|
+ preloaders_on assoc, recs, scope
+ }
end
# Not all records have the same class, so group then preload group on the reflection
@@ -125,52 +129,81 @@ module ActiveRecord
# Additionally, polymorphic belongs_to associations can have multiple associated
# classes, depending on the polymorphic_type field. So we group by the classes as
# well.
- def preload_one(association)
- grouped_records(association).each do |reflection, klasses|
- klasses.each do |klass, records|
- preloader_for(reflection).new(klass, records, reflection, preload_scope).run
+ def preloaders_for_one(association, records, scope)
+ grouped_records(association, records).flat_map do |reflection, klasses|
+ klasses.map do |rhs_klass, rs|
+ loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
+ loader.run self
+ loader
end
end
end
- def grouped_records(association)
- Hash[
- records_by_reflection(association).map do |reflection, records|
- [reflection, records.group_by { |record| association_klass(reflection, record) }]
- end
- ]
+ def grouped_records(association, records)
+ reflection_records = records_by_reflection(association, records)
+
+ reflection_records.each_with_object({}) do |(reflection, r_records),h|
+ h[reflection] = r_records.group_by { |record|
+ association_klass(reflection, record)
+ }
+ end
end
- def records_by_reflection(association)
+ def records_by_reflection(association, records)
records.group_by do |record|
- reflection = record.class.reflections[association]
+ reflection = record.class.reflect_on_association(association)
- unless reflection
- raise ActiveRecord::ConfigurationError, "Association named '#{association}' was not found; " \
- "perhaps you misspelled it?"
- end
-
- reflection
+ reflection || raise_config_error(record, association)
end
end
+ def raise_config_error(record, association)
+ raise ActiveRecord::ConfigurationError,
+ "Association named '#{association}' was not found on #{record.class.name}; " \
+ "perhaps you misspelled it?"
+ end
+
def association_klass(reflection, record)
if reflection.macro == :belongs_to && reflection.options[:polymorphic]
- klass = record.send(reflection.foreign_type)
+ klass = record.read_attribute(reflection.foreign_type.to_s)
klass && klass.constantize
else
reflection.klass
end
end
- def preloader_for(reflection)
+ class AlreadyLoaded
+ attr_reader :owners, :reflection
+
+ def initialize(klass, owners, reflection, preload_scope)
+ @owners = owners
+ @reflection = reflection
+ end
+
+ def run(preloader); end
+
+ def preloaded_records
+ owners.flat_map { |owner| owner.read_attribute reflection.name }
+ end
+ end
+
+ class NullPreloader
+ def self.new(klass, owners, reflection, preload_scope); self; end
+ def self.run(preloader); end
+ end
+
+ def preloader_for(reflection, owners, rhs_klass)
+ return NullPreloader unless rhs_klass
+
+ if owners.first.association(reflection.name).loaded?
+ return AlreadyLoaded
+ end
+
case reflection.macro
when :has_many
reflection.options[:through] ? HasManyThrough : HasMany
when :has_one
reflection.options[:through] ? HasOneThrough : HasOne
- when :has_and_belongs_to_many
- HasAndBelongsToMany
when :belongs_to
BelongsTo
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 928da71eed..69b65982b3 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -3,6 +3,7 @@ module ActiveRecord
class Preloader
class Association #:nodoc:
attr_reader :owners, :reflection, :preload_scope, :model, :klass
+ attr_reader :preloaded_records
def initialize(klass, owners, reflection, preload_scope)
@klass = klass
@@ -12,15 +13,14 @@ module ActiveRecord
@model = owners.first && owners.first.class
@scope = nil
@owners_by_key = nil
+ @preloaded_records = []
end
- def run
- unless owners.first.association(reflection.name).loaded?
- preload
- end
+ def run(preloader)
+ preload(preloader)
end
- def preload
+ def preload(preloader)
raise NotImplementedError
end
@@ -68,36 +68,39 @@ module ActiveRecord
private
- def associated_records_by_owner
+ def associated_records_by_owner(preloader)
owners_map = owners_by_key
owner_keys = owners_map.keys.compact
# Each record may have multiple owners, and vice-versa
- records_by_owner = Hash[owners.map { |owner| [owner, []] }]
+ records_by_owner = owners.each_with_object({}) do |owner,h|
+ h[owner] = []
+ end
- if klass && owner_keys.any?
+ if owner_keys.any?
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
# Make several smaller queries if necessary or make one query if the adapter supports it
sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
- sliced.each { |slice|
- records = records_for(slice)
- caster = type_caster(records, association_key_name)
- records.each do |record|
- owner_key = caster.call record[association_key_name]
-
- owners_map[owner_key].each do |owner|
- records_by_owner[owner] << record
- end
+
+ records = load_slices sliced
+ records.each do |record, owner_key|
+ owners_map[owner_key].each do |owner|
+ records_by_owner[owner] << record
end
- }
+ end
end
records_by_owner
end
- IDENTITY = lambda { |value| value }
- def type_caster(results, name)
- IDENTITY
+ def load_slices(slices)
+ @preloaded_records = slices.flat_map { |slice|
+ records_for(slice)
+ }
+
+ @preloaded_records.map { |record|
+ [record, record[association_key_name]]
+ }
end
def reflection_scope
@@ -116,6 +119,14 @@ module ActiveRecord
scope.select! preload_values[:select] || values[:select] || table[Arel.star]
scope.includes! preload_values[:includes] || values[:includes]
+ if preload_values.key? :order
+ scope.order! preload_values[:order]
+ else
+ if values.key? :order
+ scope.order! values[:order]
+ end
+ end
+
if options[:as]
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
end
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
index e6cd35e7a1..5adffcd831 100644
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb
@@ -9,8 +9,8 @@ module ActiveRecord
super.order(preload_scope.values[:order] || reflection_scope.values[:order])
end
- def preload
- associated_records_by_owner.each do |owner, records|
+ def preload(preloader)
+ associated_records_by_owner(preloader).each do |owner, records|
association = owner.association(reflection.name)
association.loaded!
association.target.concat(records)
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
deleted file mode 100644
index c042a44b21..0000000000
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-module ActiveRecord
- module Associations
- class Preloader
- class HasAndBelongsToMany < CollectionAssociation #:nodoc:
- attr_reader :join_table
-
- def initialize(klass, records, reflection, preload_options)
- super
- @join_table = Arel::Table.new(reflection.join_table).alias('t0')
- end
-
- # Unlike the other associations, we want to get a raw array of rows so that we can
- # access the aliased column on the join table
- def records_for(ids)
- scope = query_scope ids
- klass.connection.select_all(scope.arel, 'SQL', scope.bind_values)
- end
-
- def owner_key_name
- reflection.active_record_primary_key
- end
-
- def association_key_name
- 'ar_association_key_name'
- end
-
- def association_key
- join_table[reflection.foreign_key]
- end
-
- private
-
- # Once we have used the join table column (in super), we manually instantiate the
- # actual records, ensuring that we don't create more than one instances of the same
- # record
- def associated_records_by_owner
- records = {}
- super.each_value do |rows|
- rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) }
- end
- end
-
- def type_caster(results, name)
- caster = results.column_types.fetch(name, results.identity_type)
- lambda { |value| caster.type_cast value }
- end
-
- def build_scope
- super.joins(join).select(join_select)
- end
-
- def join_select
- association_key.as(Arel.sql(association_key_name))
- end
-
- def join
- condition = table[reflection.association_primary_key].eq(
- join_table[reflection.association_foreign_key])
-
- table.create_join(join_table, table.create_on(condition))
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index 157b627ad5..7b37b5942d 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -4,7 +4,7 @@ module ActiveRecord
class HasManyThrough < CollectionAssociation #:nodoc:
include ThroughAssociation
- def associated_records_by_owner
+ def associated_records_by_owner(preloader)
records_by_owner = super
if reflection_scope.distinct_value
diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb
index 44e804d785..2b5cfda8ce 100644
--- a/activerecord/lib/active_record/associations/preloader/singular_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb
@@ -5,8 +5,8 @@ module ActiveRecord
private
- def preload
- associated_records_by_owner.each do |owner, associated_records|
+ def preload(preloader)
+ associated_records_by_owner(preloader).each do |owner, associated_records|
record = associated_records.first
association = owner.association(reflection.name)
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 2c625cec04..2a8530af62 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Associations
class Preloader
module ThroughAssociation #:nodoc:
-
def through_reflection
reflection.through_reflection
end
@@ -11,34 +10,68 @@ module ActiveRecord
reflection.source_reflection
end
- def associated_records_by_owner
- through_records = through_records_by_owner
+ def associated_records_by_owner(preloader)
+ preloader.preload(owners,
+ through_reflection.name,
+ through_scope)
+
+ through_records = owners.map do |owner|
+ association = owner.association through_reflection.name
+
+ [owner, Array(association.reader)]
+ end
+
+ reset_association owners, through_reflection.name
+
+ middle_records = through_records.map { |(_,rec)| rec }.flatten
+
+ preloaders = preloader.preload(middle_records,
+ source_reflection.name,
+ reflection_scope)
- Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run
+ @preloaded_records = preloaders.flat_map(&:preloaded_records)
+
+ middle_to_pl = preloaders.each_with_object({}) do |pl,h|
+ pl.owners.each { |middle|
+ h[middle] = pl
+ }
+ end
- through_records.each do |owner, records|
- records.map! { |r| r.send(source_reflection.name) }.flatten!
- records.compact!
+ record_offset = {}
+ @preloaded_records.each_with_index do |record,i|
+ record_offset[record] = i
end
+
+ through_records.each_with_object({}) { |(lhs,center),records_by_owner|
+ pl_to_middle = center.group_by { |record| middle_to_pl[record] }
+
+ records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
+ rhs_records = middles.flat_map { |r|
+ association = r.association source_reflection.name
+
+ association.reader
+ }.compact
+
+ rhs_records.sort_by { |rhs| record_offset[rhs] }
+ end
+ }
end
private
- def through_records_by_owner
- Preloader.new(owners, through_reflection.name, through_scope).run
-
+ def reset_association(owners, association_name)
should_reset = (through_scope != through_reflection.klass.unscoped) ||
(reflection.options[:source_type] && through_reflection.collection?)
- owners.each_with_object({}) do |owner, h|
- association = owner.association through_reflection.name
- h[owner] = Array(association.reader)
-
- # Dont cache the association - we would only be caching a subset
- association.reset if should_reset
+ # Dont cache the association - we would only be caching a subset
+ if should_reset
+ owners.each { |owner|
+ owner.association(association_name).reset
+ }
end
end
+
def through_scope
scope = through_reflection.klass.unscoped
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 4f06955406..30fa2c8ba5 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -12,6 +12,9 @@ module ActiveRecord
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
def assign_attributes(new_attributes)
+ if !new_attributes.respond_to?(:stringify_keys)
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
+ end
return if new_attributes.blank?
attributes = new_attributes.stringify_keys
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 208da2cb77..3924eec872 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/enumerable'
require 'mutex_m'
+require 'thread_safe'
module ActiveRecord
# = Active Record Attribute Methods
@@ -29,23 +30,18 @@ module ActiveRecord
}
class AttributeMethodCache
- include Mutex_m
-
def initialize
- super
@module = Module.new
- @method_cache = {}
+ @method_cache = ThreadSafe::Cache.new
end
def [](name)
- synchronize do
- @method_cache.fetch(name) {
- safe_name = name.unpack('h*').first
- temp_method = "__temp__#{safe_name}"
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
- @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
- @method_cache[name] = @module.instance_method temp_method
- }
+ @method_cache.compute_if_absent(name) do
+ safe_name = name.unpack('h*').first
+ temp_method = "__temp__#{safe_name}"
+ ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
+ @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
+ @module.instance_method temp_method
end
end
@@ -165,12 +161,9 @@ module ActiveRecord
# If we haven't generated any methods yet, generate them, then
# see if we've created the method we're looking for.
def method_missing(method, *args, &block) # :nodoc:
- if self.class.define_attribute_methods
- if respond_to_without_attributes?(method)
- send(method, *args, &block)
- else
- super
- end
+ self.class.define_attribute_methods
+ if respond_to_without_attributes?(method)
+ send(method, *args, &block)
else
super
end
@@ -252,9 +245,10 @@ module ActiveRecord
# Returns an <tt>#inspect</tt>-like string for the value of the
# attribute +attr_name+. String attributes are truncated upto 50
- # characters, and Date and Time attributes are returned in the
- # <tt>:db</tt> format. Other attributes return the value of
- # <tt>#inspect</tt> without modification.
+ # characters, Date and Time attributes are returned in the
+ # <tt>:db</tt> format, Array attributes are truncated upto 10 values.
+ # Other attributes return the value of <tt>#inspect</tt> without
+ # modification.
#
# person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
#
@@ -263,6 +257,9 @@ module ActiveRecord
#
# person.attribute_for_inspect(:created_at)
# # => "\"2012-10-22 00:15:07\""
+ #
+ # person.attribute_for_inspect(:tag_ids)
+ # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
def attribute_for_inspect(attr_name)
value = read_attribute(attr_name)
@@ -270,6 +267,9 @@ module ActiveRecord
"#{value[0, 50]}...".inspect
elsif value.is_a?(Date) || value.is_a?(Time)
%("#{value.to_s(:db)}")
+ elsif value.is_a?(Array) && value.size > 10
+ inspected = value.first(10).inspect
+ %(#{inspected[0...-1]}, ...])
else
value.inspect
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index dc2399643c..19e81abba5 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -19,8 +19,7 @@ module ActiveRecord
# Attempts to +save+ the record and clears changed attributes if successful.
def save(*)
if status = super
- @previously_changed = changes
- @changed_attributes.clear
+ changes_applied
end
status
end
@@ -28,16 +27,14 @@ module ActiveRecord
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
def save!(*)
super.tap do
- @previously_changed = changes
- @changed_attributes.clear
+ changes_applied
end
end
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
- @previously_changed.clear
- @changed_attributes.clear
+ reset_changes
end
end
@@ -48,11 +45,11 @@ module ActiveRecord
# The attribute already has an unsaved change.
if attribute_changed?(attr)
- old = @changed_attributes[attr]
- @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
+ old = changed_attributes[attr]
+ changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
else
old = clone_attribute_value(:read_attribute, attr)
- @changed_attributes[attr] = old if _field_changed?(attr, old, value)
+ changed_attributes[attr] = old if _field_changed?(attr, old, value)
end
# Carry on.
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 1cf3aba41c..c152a246b5 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -109,13 +109,14 @@ module ActiveRecord
# We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829.
name = attr_name.to_s
@attributes_cache[name] || @attributes_cache.fetch(name) {
- column = @columns_hash.fetch(name) {
- return @attributes.fetch(name) {
- if name == 'id' && self.class.primary_key != name
- read_attribute(self.class.primary_key)
- end
- }
- }
+ column = @column_types_override[name] if @column_types_override
+ column ||= @column_types[name]
+
+ return @attributes.fetch(name) {
+ if name == 'id' && self.class.primary_key != name
+ read_attribute(self.class.primary_key)
+ end
+ } unless column
value = @attributes.fetch(name) {
return block_given? ? yield(name) : nil
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 1287de0d0d..5701804168 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -66,6 +66,10 @@ module ActiveRecord
def type
@column.type
end
+
+ def accessor
+ ActiveRecord::Store::IndifferentHashAccessor
+ end
end
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index b30d1eb0a6..e9622ca0c1 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -143,71 +143,71 @@ module ActiveRecord
module ClassMethods
private
- def define_non_cyclic_method(name, reflection, &block)
- define_method(name) do |*args|
- result = true; @_already_called ||= {}
- # Loop prevention for validation of associations
- unless @_already_called[[name, reflection.name]]
- begin
- @_already_called[[name, reflection.name]]=true
- result = instance_eval(&block)
- ensure
- @_already_called[[name, reflection.name]]=false
+ def define_non_cyclic_method(name, &block)
+ define_method(name) do |*args|
+ result = true; @_already_called ||= {}
+ # Loop prevention for validation of associations
+ unless @_already_called[name]
+ begin
+ @_already_called[name]=true
+ result = instance_eval(&block)
+ ensure
+ @_already_called[name]=false
+ end
end
- end
- result
+ result
+ end
end
- end
- # Adds validation and save callbacks for the association as specified by
- # the +reflection+.
- #
- # For performance reasons, we don't check whether to validate at runtime.
- # However the validation and callback methods are lazy and those methods
- # get created when they are invoked for the very first time. However,
- # this can change, for instance, when using nested attributes, which is
- # called _after_ the association has been defined. Since we don't want
- # the callbacks to get defined multiple times, there are guards that
- # check if the save or validation methods have already been defined
- # before actually defining them.
- def add_autosave_association_callbacks(reflection)
- save_method = :"autosave_associated_records_for_#{reflection.name}"
- validation_method = :"validate_associated_records_for_#{reflection.name}"
- collection = reflection.collection?
-
- unless method_defined?(save_method)
- if collection
- before_save :before_save_collection_association
-
- define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
- after_create save_method
- after_update save_method
- elsif reflection.macro == :has_one
- define_method(save_method) { save_has_one_association(reflection) }
- # Configures two callbacks instead of a single after_save so that
- # the model may rely on their execution order relative to its
- # own callbacks.
- #
- # For example, given that after_creates run before after_saves, if
- # we configured instead an after_save there would be no way to fire
- # a custom after_create callback after the child association gets
- # created.
- after_create save_method
- after_update save_method
- else
- define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
- before_save save_method
+ # Adds validation and save callbacks for the association as specified by
+ # the +reflection+.
+ #
+ # For performance reasons, we don't check whether to validate at runtime.
+ # However the validation and callback methods are lazy and those methods
+ # get created when they are invoked for the very first time. However,
+ # this can change, for instance, when using nested attributes, which is
+ # called _after_ the association has been defined. Since we don't want
+ # the callbacks to get defined multiple times, there are guards that
+ # check if the save or validation methods have already been defined
+ # before actually defining them.
+ def add_autosave_association_callbacks(reflection)
+ save_method = :"autosave_associated_records_for_#{reflection.name}"
+ validation_method = :"validate_associated_records_for_#{reflection.name}"
+ collection = reflection.collection?
+
+ unless method_defined?(save_method)
+ if collection
+ before_save :before_save_collection_association
+
+ define_non_cyclic_method(save_method) { save_collection_association(reflection) }
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
+ after_create save_method
+ after_update save_method
+ elsif reflection.macro == :has_one
+ define_method(save_method) { save_has_one_association(reflection) }
+ # Configures two callbacks instead of a single after_save so that
+ # the model may rely on their execution order relative to its
+ # own callbacks.
+ #
+ # For example, given that after_creates run before after_saves, if
+ # we configured instead an after_save there would be no way to fire
+ # a custom after_create callback after the child association gets
+ # created.
+ after_create save_method
+ after_update save_method
+ else
+ define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
+ before_save save_method
+ end
end
- end
- if reflection.validate? && !method_defined?(validation_method)
- method = (collection ? :validate_collection_association : :validate_single_association)
- define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
- validate validation_method
+ if reflection.validate? && !method_defined?(validation_method)
+ method = (collection ? :validate_collection_association : :validate_single_association)
+ define_non_cyclic_method(validation_method) { send(method, reflection) }
+ validate validation_method
+ end
end
- end
end
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
@@ -254,173 +254,179 @@ module ActiveRecord
private
- # Returns the record for an association collection that should be validated
- # or saved. If +autosave+ is +false+ only new records will be returned,
- # unless the parent is/was a new record itself.
- def associated_records_to_validate_or_save(association, new_record, autosave)
- if new_record
- association && association.target
- elsif autosave
- association.target.find_all { |record| record.changed_for_autosave? }
- else
- association.target.find_all { |record| record.new_record? }
+ # Returns the record for an association collection that should be validated
+ # or saved. If +autosave+ is +false+ only new records will be returned,
+ # unless the parent is/was a new record itself.
+ def associated_records_to_validate_or_save(association, new_record, autosave)
+ if new_record
+ association && association.target
+ elsif autosave
+ association.target.find_all { |record| record.changed_for_autosave? }
+ else
+ association.target.find_all { |record| record.new_record? }
+ end
end
- end
- # go through nested autosave associations that are loaded in memory (without loading
- # any new ones), and return true if is changed for autosave
- def nested_records_changed_for_autosave?
- self.class.reflect_on_all_autosave_associations.any? do |reflection|
- association = association_instance_get(reflection.name)
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ # go through nested autosave associations that are loaded in memory (without loading
+ # any new ones), and return true if is changed for autosave
+ def nested_records_changed_for_autosave?
+ self.class.reflect_on_all_autosave_associations.any? do |reflection|
+ association = association_instance_get(reflection.name)
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
+ end
end
- end
- # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
- # turned on for the association.
- def validate_single_association(reflection)
- association = association_instance_get(reflection.name)
- record = association && association.reader
- association_valid?(reflection, record) if record
- end
+ # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
+ # turned on for the association.
+ def validate_single_association(reflection)
+ association = association_instance_get(reflection.name)
+ record = association && association.reader
+ association_valid?(reflection, record) if record
+ end
- # Validate the associated records if <tt>:validate</tt> or
- # <tt>:autosave</tt> is turned on for the association specified by
- # +reflection+.
- def validate_collection_association(reflection)
- if association = association_instance_get(reflection.name)
- if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
- records.each { |record| association_valid?(reflection, record) }
+ # Validate the associated records if <tt>:validate</tt> or
+ # <tt>:autosave</tt> is turned on for the association specified by
+ # +reflection+.
+ def validate_collection_association(reflection)
+ if association = association_instance_get(reflection.name)
+ if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
+ records.each { |record| association_valid?(reflection, record) }
+ end
end
end
- end
- # Returns whether or not the association is valid and applies any errors to
- # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
- # enabled records if they're marked_for_destruction? or destroyed.
- def association_valid?(reflection, record)
- return true if record.destroyed? || record.marked_for_destruction?
-
- unless valid = record.valid?
- if reflection.options[:autosave]
- record.errors.each do |attribute, message|
- attribute = "#{reflection.name}.#{attribute}"
- errors[attribute] << message
- errors[attribute].uniq!
+ # Returns whether or not the association is valid and applies any errors to
+ # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
+ # enabled records if they're marked_for_destruction? or destroyed.
+ def association_valid?(reflection, record)
+ return true if record.destroyed? || record.marked_for_destruction?
+
+ unless valid = record.valid?
+ if reflection.options[:autosave]
+ record.errors.each do |attribute, message|
+ attribute = "#{reflection.name}.#{attribute}"
+ errors[attribute] << message
+ errors[attribute].uniq!
+ end
+ else
+ errors.add(reflection.name)
end
- else
- errors.add(reflection.name)
end
+ valid
end
- valid
- end
- # Is used as a before_save callback to check while saving a collection
- # association whether or not the parent was a new record before saving.
- def before_save_collection_association
- @new_record_before_save = new_record?
- true
- end
+ # Is used as a before_save callback to check while saving a collection
+ # association whether or not the parent was a new record before saving.
+ def before_save_collection_association
+ @new_record_before_save = new_record?
+ true
+ end
- # Saves any new associated records, or all loaded autosave associations if
- # <tt>:autosave</tt> is enabled on the association.
- #
- # In addition, it destroys all children that were marked for destruction
- # with mark_for_destruction.
- #
- # This all happens inside a transaction, _if_ the Transactions module is included into
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
- def save_collection_association(reflection)
- if association = association_instance_get(reflection.name)
- autosave = reflection.options[:autosave]
-
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
-
- if autosave
- records_to_destroy = records.select(&:marked_for_destruction?)
- records_to_destroy.each { |record| association.destroy(record) }
- records -= records_to_destroy
- end
+ # Saves any new associated records, or all loaded autosave associations if
+ # <tt>:autosave</tt> is enabled on the association.
+ #
+ # In addition, it destroys all children that were marked for destruction
+ # with mark_for_destruction.
+ #
+ # This all happens inside a transaction, _if_ the Transactions module is included into
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
+ def save_collection_association(reflection)
+ if association = association_instance_get(reflection.name)
+ autosave = reflection.options[:autosave]
+
+ if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
+
+ if autosave
+ records_to_destroy = records.select(&:marked_for_destruction?)
+ records_to_destroy.each { |record| association.destroy(record) }
+ records -= records_to_destroy
+ end
- records.each do |record|
- next if record.destroyed?
+ records.each do |record|
+ next if record.destroyed?
- saved = true
+ saved = true
- if autosave != false && (@new_record_before_save || record.new_record?)
- if autosave
- saved = association.insert_record(record, false)
- else
- association.insert_record(record) unless reflection.nested?
+ if autosave != false && (@new_record_before_save || record.new_record?)
+ if autosave
+ saved = association.insert_record(record, false)
+ else
+ association.insert_record(record) unless reflection.nested?
+ end
+ elsif autosave
+ saved = record.save(:validate => false)
end
- elsif autosave
- saved = record.save(:validate => false)
- end
- raise ActiveRecord::Rollback unless saved
+ raise ActiveRecord::Rollback unless saved
+ end
end
- end
- # reconstruct the scope now that we know the owner's id
- association.reset_scope if association.respond_to?(:reset_scope)
+ # reconstruct the scope now that we know the owner's id
+ association.reset_scope if association.respond_to?(:reset_scope)
+ end
end
- end
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
- # on the association.
- #
- # In addition, it will destroy the association if it was marked for
- # destruction with mark_for_destruction.
- #
- # This all happens inside a transaction, _if_ the Transactions module is included into
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
- def save_has_one_association(reflection)
- association = association_instance_get(reflection.name)
- record = association && association.load_target
- if record && !record.destroyed?
- autosave = reflection.options[:autosave]
-
- if autosave && record.marked_for_destruction?
- record.destroy
- else
- key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
- if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
- unless reflection.through_reflection
- record[reflection.foreign_key] = key
- end
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
+ # on the association.
+ #
+ # In addition, it will destroy the association if it was marked for
+ # destruction with mark_for_destruction.
+ #
+ # This all happens inside a transaction, _if_ the Transactions module is included into
+ # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
+ def save_has_one_association(reflection)
+ association = association_instance_get(reflection.name)
+ record = association && association.load_target
+ if record && !record.destroyed?
+ autosave = reflection.options[:autosave]
- saved = record.save(:validate => !autosave)
- raise ActiveRecord::Rollback if !saved && autosave
- saved
+ if autosave && record.marked_for_destruction?
+ record.destroy
+ else
+ key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
+ if autosave != false && (autosave || new_record? || record_changed?(reflection, record, key))
+
+ unless reflection.through_reflection
+ record[reflection.foreign_key] = key
+ end
+
+ saved = record.save(:validate => !autosave)
+ raise ActiveRecord::Rollback if !saved && autosave
+ saved
+ end
end
end
end
- end
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
- #
- # In addition, it will destroy the association if it was marked for destruction.
- def save_belongs_to_association(reflection)
- association = association_instance_get(reflection.name)
- record = association && association.load_target
- if record && !record.destroyed?
- autosave = reflection.options[:autosave]
-
- if autosave && record.marked_for_destruction?
- self[reflection.foreign_key] = nil
- record.destroy
- elsif autosave != false
- saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
-
- if association.updated?
- association_id = record.send(reflection.options[:primary_key] || :id)
- self[reflection.foreign_key] = association_id
- association.loaded!
- end
+ # If the record is new or it has changed, returns true.
+ def record_changed?(reflection, record, key)
+ record.new_record? || record[reflection.foreign_key] != key || record.attribute_changed?(reflection.foreign_key)
+ end
- saved if autosave
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
+ #
+ # In addition, it will destroy the association if it was marked for destruction.
+ def save_belongs_to_association(reflection)
+ association = association_instance_get(reflection.name)
+ record = association && association.load_target
+ if record && !record.destroyed?
+ autosave = reflection.options[:autosave]
+
+ if autosave && record.marked_for_destruction?
+ self[reflection.foreign_key] = nil
+ record.destroy
+ elsif autosave != false
+ saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
+
+ if association.updated?
+ association_id = record.send(reflection.options[:primary_key] || :id)
+ self[reflection.foreign_key] = association_id
+ association.loaded!
+ end
+
+ saved if autosave
+ end
end
end
- end
end
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index e4c484d64b..128a9377c1 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -23,11 +23,14 @@ module ActiveRecord
# Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
# <tt>after_rollback</tt>.
#
+ # Additionally, an <tt>after_touch</tt> callback is triggered whenever an
+ # object is touched.
+ #
# Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
# are instantiated as well.
#
- # That's a total of twelve callbacks, which gives you immense power to react and prepare for each state in the
+ # There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
# Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
# except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
#
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 32244b1755..e1f29ea03a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -377,7 +377,7 @@ module ActiveRecord
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
[sql, binds]
end
-
+
def last_inserted_id(result)
row = result.rows.first
row && row.first
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 7aae297cdc..8399232d73 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -9,10 +9,10 @@ module ActiveRecord
def dirties_query_cache(base, *method_names)
method_names.each do |method_name|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
- def #{method_name}(*) # def update_with_query_dirty(*)
- clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
- super # super
- end # end
+ def #{method_name}(*)
+ clear_query_cache if @query_cache_enabled
+ super
+ end
end_code
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
new file mode 100644
index 0000000000..25c17ce971
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module Savepoints #:nodoc:
+ def supports_savepoints?
+ true
+ end
+
+ def create_savepoint(name = current_savepoint_name)
+ execute("SAVEPOINT #{name}")
+ end
+
+ def rollback_to_savepoint(name = current_savepoint_name)
+ execute("ROLLBACK TO SAVEPOINT #{name}")
+ end
+
+ def release_savepoint(name = current_savepoint_name)
+ execute("RELEASE SAVEPOINT #{name}")
+ end
+ 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 ba1cb05d2c..cbe563676b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -33,6 +33,7 @@ module ActiveRecord
autoload :Quoting
autoload :ConnectionPool
autoload :QueryCache
+ autoload :Savepoints
end
autoload_at 'active_record/connection_adapters/abstract/transaction' do
@@ -97,6 +98,7 @@ module ActiveRecord
@pool = pool
@schema_cache = SchemaCache.new self
@visitor = nil
+ @prepared_statements = false
end
def valid_type?(type)
@@ -208,10 +210,11 @@ module ActiveRecord
end
def unprepared_statement
- old, @visitor = @visitor, unprepared_visitor
+ old_prepared_statements, @prepared_statements = @prepared_statements, false
+ old_visitor, @visitor = @visitor, unprepared_visitor
yield
ensure
- @visitor = old
+ @visitor, @prepared_statements = old_visitor, old_prepared_statements
end
# Returns the human-readable name of the adapter. Use mixed case - one
@@ -393,13 +396,13 @@ module ActiveRecord
@transaction.number
end
- def create_savepoint
+ def create_savepoint(name = nil)
end
- def rollback_to_savepoint
+ def rollback_to_savepoint(name = nil)
end
- def release_savepoint
+ def release_savepoint(name = nil)
end
def case_sensitive_modifier(node)
@@ -421,13 +424,14 @@ module ActiveRecord
protected
- def log(sql, name = "SQL", binds = [])
+ def log(sql, name = "SQL", binds = [], statement_name = nil)
@instrumenter.instrument(
"sql.active_record",
- :sql => sql,
- :name => name,
- :connection_id => object_id,
- :binds => binds) { yield }
+ :sql => sql,
+ :name => name,
+ :connection_id => object_id,
+ :statement_name => statement_name,
+ :binds => binds) { yield }
rescue => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger
@@ -440,6 +444,10 @@ module ActiveRecord
# override in derived class
ActiveRecord::StatementInvalid.new(message, exception)
end
+
+ def without_prepared_statement?(binds)
+ !@prepared_statements || binds.empty?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 4b11ea795c..138ab811dc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -3,6 +3,8 @@ require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ include Savepoints
+
class SchemaCreation < AbstractAdapter::SchemaCreation
def visit_AddColumn(o)
@@ -174,6 +176,7 @@ module ActiveRecord
@quoted_column_names, @quoted_table_names = {}, {}
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
+ @prepared_statements = true
@visitor = Arel::Visitors::MySQL.new self
else
@visitor = unprepared_visitor
@@ -193,11 +196,6 @@ module ActiveRecord
true
end
- # Returns true, since this connection adapter supports savepoints.
- def supports_savepoints?
- true
- end
-
def supports_bulk_alter? #:nodoc:
true
end
@@ -339,18 +337,6 @@ module ActiveRecord
# Transactions aren't supported
end
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
# these, we must use a subquery.
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index fb53090edc..f2fbd5a8f2 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -13,7 +13,7 @@ module ActiveRecord
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
end
- attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function
attr_accessor :primary, :coder
alias :encoded? :coder
@@ -27,16 +27,17 @@ module ActiveRecord
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
# +null+ determines if this column allows +NULL+ values.
def initialize(name, default, sql_type = nil, null = true)
- @name = name
- @sql_type = sql_type
- @null = null
- @limit = extract_limit(sql_type)
- @precision = extract_precision(sql_type)
- @scale = extract_scale(sql_type)
- @type = simplified_type(sql_type)
- @default = extract_default(default)
- @primary = nil
- @coder = nil
+ @name = name
+ @sql_type = sql_type
+ @null = null
+ @limit = extract_limit(sql_type)
+ @precision = extract_precision(sql_type)
+ @scale = extract_scale(sql_type)
+ @type = simplified_type(sql_type)
+ @default = extract_default(default)
+ @default_function = nil
+ @primary = nil
+ @coder = nil
end
# Returns +true+ if the column is either of type string or text.
@@ -203,11 +204,19 @@ module ActiveRecord
end
end
- def new_time(year, mon, mday, hour, min, sec, microsec)
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
# Treat 0000-00-00 00:00:00 as nil.
return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
- Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ if offset
+ time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
+ return nil unless time
+
+ time -= offset
+ Base.default_timezone == :utc ? time : time.getlocal
+ else
+ Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ end
end
def fast_string_to_date(string)
@@ -232,7 +241,7 @@ module ActiveRecord
time_hash = Date._parse(string)
time_hash[:sec_fraction] = microseconds(time_hash)
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 8bad7d0cf5..64fc9e95d8 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -55,7 +55,7 @@ module ActiveRecord
begin
require path_to_adapter
rescue Gem::LoadError => e
- raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile."
+ raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)."
rescue LoadError => e
raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 92796c996e..e790f731ea 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -229,7 +229,7 @@ module ActiveRecord
alias exec_without_stmt exec_query
- # Returns an ActiveRecord::Result instance.
+ # Returns an ActiveRecord::Result instance.
def select(sql, name = nil, binds = [])
exec_query(sql, name)
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 15b5452850..88c9494fc6 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -279,11 +279,7 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- # If the configuration sets prepared_statements:false, binds will
- # always be empty, since the bind variables will have been already
- # substituted and removed from binds by BindVisitor, so this will
- # effectively disable prepared statement usage completely.
- if binds.empty?
+ if without_prepared_statement?(binds)
result_set, affected_rows = exec_without_stmt(sql, name)
else
result_set, affected_rows = exec_stmt(sql, name, binds)
@@ -563,7 +559,7 @@ module ActiveRecord
def set_field_encoding field_name
field_name.force_encoding(client_encoding)
if internal_enc = Encoding.default_internal
- field_name = field_name.encoding(internal_enc)
+ field_name = field_name.encode!(internal_enc)
end
field_name
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 527d13b9b0..fa173d13a2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -134,35 +134,31 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- log(sql, name, binds) do
- result = binds.empty? ? exec_no_cache(sql, binds) :
- exec_cache(sql, binds)
-
- types = {}
- fields = result.fields
- fields.each_with_index do |fname, i|
- ftype = result.ftype i
- fmod = result.fmod i
- types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
- warn "unknown OID: #{fname}(#{oid}) (#{sql})"
- OID::Identity.new
- }
- end
-
- ret = ActiveRecord::Result.new(fields, result.values, types)
- result.clear
- return ret
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
+ exec_cache(sql, name, binds)
+
+ types = {}
+ fields = result.fields
+ fields.each_with_index do |fname, i|
+ ftype = result.ftype i
+ fmod = result.fmod i
+ types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
+ warn "unknown OID: #{fname}(#{oid}) (#{sql})"
+ OID::Identity.new
+ }
end
+
+ ret = ActiveRecord::Result.new(fields, result.values, types)
+ result.clear
+ return ret
end
def exec_delete(sql, name = 'SQL', binds = [])
- log(sql, name, binds) do
- result = binds.empty? ? exec_no_cache(sql, binds) :
- exec_cache(sql, binds)
- affected = result.cmd_tuples
- result.clear
- affected
- end
+ result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
+ exec_cache(sql, name, binds)
+ affected = result.cmd_tuples
+ result.clear
+ affected
end
alias :exec_update :exec_delete
@@ -218,18 +214,6 @@ module ActiveRecord
def rollback_db_transaction
execute "ROLLBACK"
end
-
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index dab876af14..6c5792954f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -234,6 +234,10 @@ module ActiveRecord
ConnectionAdapters::PostgreSQLColumn.string_to_hstore value
end
+
+ def accessor
+ ActiveRecord::Store::StringKeyedHashAccessor
+ end
end
class Cidr < Type
@@ -245,11 +249,19 @@ module ActiveRecord
end
class Json < Type
+ def type_cast_for_write(value)
+ ConnectionAdapters::PostgreSQLColumn.json_to_string value
+ end
+
def type_cast(value)
return if value.nil?
ConnectionAdapters::PostgreSQLColumn.string_to_json value
end
+
+ def accessor
+ ActiveRecord::Store::StringKeyedHashAccessor
+ end
end
class TypeMap
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 3fce8de1ba..5dc70a5ad1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -46,7 +46,7 @@ module ActiveRecord
end
# Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
- # <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>,
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
# <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
# <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
#
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 5b9453a579..3668aecd4b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -49,13 +49,17 @@ module ActiveRecord
# Instantiates a new PostgreSQL column definition in a table.
def initialize(name, default, oid_type, sql_type = nil, null = true)
@oid_type = oid_type
+ default_value = self.class.extract_value_from_default(default)
+
if sql_type =~ /\[\]$/
@array = true
- super(name, self.class.extract_value_from_default(default), sql_type[0..sql_type.length - 3], null)
+ super(name, default_value, sql_type[0..sql_type.length - 3], null)
else
@array = false
- super(name, self.class.extract_value_from_default(default), sql_type, null)
+ super(name, default_value, sql_type, null)
end
+
+ @default_function = default if has_default_function?(default_value, default)
end
# :stopdoc:
@@ -144,8 +148,16 @@ module ActiveRecord
@oid_type.type_cast value
end
+ def accessor
+ @oid_type.accessor
+ end
+
private
+ def has_default_function?(default_value, default)
+ !default_value && (%r{\w+\(.*\)} === default)
+ end
+
def extract_limit(sql_type)
case sql_type
when /^bigint/i; 8
@@ -428,6 +440,7 @@ module ActiveRecord
include ReferentialIntegrity
include SchemaStatements
include DatabaseStatements
+ include Savepoints
# Returns 'PostgreSQL' as adapter name for identification purposes.
def adapter_name
@@ -439,6 +452,7 @@ module ActiveRecord
def prepare_column_options(column, types)
spec = super
spec[:array] = 'true' if column.respond_to?(:array) && column.array
+ spec[:default] = "\"#{column.default_function}\"" if column.default_function
spec
end
@@ -531,6 +545,7 @@ module ActiveRecord
super(connection, logger)
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
+ @prepared_statements = true
@visitor = Arel::Visitors::PostgreSQL.new self
else
@visitor = unprepared_visitor
@@ -616,11 +631,6 @@ module ActiveRecord
true
end
- # Returns true, since this connection adapter supports savepoints.
- def supports_savepoints?
- true
- end
-
# Returns true.
def supports_explain?
true
@@ -767,27 +777,29 @@ module ActiveRecord
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
- def exec_no_cache(sql, binds)
- @connection.async_exec(sql)
+ def exec_no_cache(sql, name, binds)
+ log(sql, name, binds) { @connection.async_exec(sql) }
end
- def exec_cache(sql, binds)
+ def exec_cache(sql, name, binds)
stmt_key = prepare_statement(sql)
- # Clear the queue
- @connection.get_last_result
- @connection.send_query_prepared(stmt_key, binds.map { |col, val|
- type_cast(val, col)
- })
- @connection.block
- @connection.get_last_result
- rescue PGError => e
+ log(sql, name, binds, stmt_key) do
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
+ type_cast(val, col)
+ })
+ @connection.block
+ @connection.get_last_result
+ end
+ rescue ActiveRecord::StatementInvalid => e
+ pgerror = e.original_exception
+
# Get the PG code for the failure. Annoyingly, the code for
# prepared statements whose return value may have changed is
# FEATURE_NOT_SUPPORTED. Check here for more details:
# http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
begin
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
rescue
raise e
end
@@ -812,6 +824,8 @@ module ActiveRecord
unless @statements.key? sql_key
nextkey = @statements.next_key
@connection.prepare nextkey, sql
+ # Clear the queue
+ @connection.get_last_result
@statements[sql_key] = nextkey
end
@statements[sql_key]
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index df489a5b1f..e5ad08b6b0 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -53,6 +53,23 @@ module ActiveRecord
#
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
+ include Savepoints
+
+ NATIVE_DATABASE_TYPES = {
+ primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
+ string: { name: "varchar", limit: 255 },
+ text: { name: "text" },
+ integer: { name: "integer" },
+ float: { name: "float" },
+ decimal: { name: "decimal" },
+ datetime: { name: "datetime" },
+ timestamp: { name: "datetime" },
+ time: { name: "time" },
+ date: { name: "date" },
+ binary: { name: "blob" },
+ boolean: { name: "boolean" }
+ }
+
class Version
include Comparable
@@ -113,6 +130,7 @@ module ActiveRecord
@config = config
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
+ @prepared_statements = true
@visitor = Arel::Visitors::SQLite.new self
else
@visitor = unprepared_visitor
@@ -180,11 +198,6 @@ module ActiveRecord
true
end
- # Returns true
- def supports_autoincrement? #:nodoc:
- true
- end
-
def supports_index_sort_order?
true
end
@@ -197,20 +210,7 @@ module ActiveRecord
end
def native_database_types #:nodoc:
- {
- :primary_key => default_primary_key_type,
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "integer" },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "boolean" }
- }
+ NATIVE_DATABASE_TYPES
end
# Returns the current database encoding format as a string, eg: 'UTF-8'
@@ -293,8 +293,8 @@ module ActiveRecord
def exec_query(sql, name = nil, binds = [])
log(sql, name, binds) do
- # Don't cache statements without bind values
- if binds.empty?
+ # Don't cache statements if they are not prepared
+ if without_prepared_statement?(binds)
stmt = @connection.prepare(sql)
cols = stmt.columns
records = stmt.to_a
@@ -350,18 +350,6 @@ module ActiveRecord
exec_query(sql, name).rows
end
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
def begin_db_transaction #:nodoc:
log('begin transaction',nil) { @connection.transaction }
end
@@ -605,14 +593,6 @@ module ActiveRecord
@sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
end
- def default_primary_key_type
- if supports_autoincrement?
- 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
- else
- 'INTEGER PRIMARY KEY NOT NULL'
- end
- end
-
def translate_exception(exception, message)
case exception.message
when /column(s)? .* (is|are) not unique/
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 2b1e997ef4..366ebde418 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -163,19 +163,22 @@ module ActiveRecord
# ==== Example:
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
- def initialize(attributes = nil)
+ def initialize(attributes = nil, options = {})
defaults = self.class.column_defaults.dup
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
@attributes = self.class.initialize_attributes(defaults)
- @columns_hash = self.class.column_types.dup
+ @column_types_override = nil
+ @column_types = self.class.column_types
init_internals
init_changed_attributes
ensure_proper_type
populate_with_current_scope_attributes
- assign_attributes(attributes) if attributes
+ # +options+ argument is only needed to make protected_attributes gem easier to hook.
+ # Remove it when we drop support to this gem.
+ init_attributes(attributes, options) if attributes
yield self if block_given?
run_callbacks :initialize unless _initialize_callbacks.empty?
@@ -193,7 +196,8 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
@attributes = self.class.initialize_attributes(coder['attributes'])
- @columns_hash = self.class.column_types.merge(coder['column_types'] || {})
+ @column_types_override = coder['column_types']
+ @column_types = self.class.column_types
init_internals
@@ -282,7 +286,7 @@ module ActiveRecord
def ==(comparison_object)
super ||
comparison_object.instance_of?(self.class) &&
- id.present? &&
+ id &&
comparison_object.id == id
end
alias :eql? :==
@@ -306,13 +310,6 @@ module ActiveRecord
@attributes.frozen?
end
- # Allows sort on objects
- def <=>(other_object)
- if other_object.is_a?(self.class)
- self.to_key <=> other_object.to_key
- end
- end
-
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
# attributes will be marked as read only since they cannot be saved.
def readonly?
@@ -416,8 +413,6 @@ module ActiveRecord
@aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
- @previously_changed = {}
- @changed_attributes = {}
@readonly = false
@destroyed = false
@marked_for_destruction = false
@@ -434,8 +429,14 @@ module ActiveRecord
# optimistic locking) won't get written unless they get marked as changed
self.class.columns.each do |c|
attr, orig_value = c.name, c.default
- @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
end
end
+
+ # This method is needed to make protected_attributes gem easier to hook.
+ # Remove it when we drop support to this gem.
+ def init_attributes(attributes, options)
+ assign_attributes(attributes)
+ end
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 40263b2a70..3bb3131bd1 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -436,9 +436,47 @@ module ActiveRecord
cattr_accessor :all_loaded_fixtures
self.all_loaded_fixtures = {}
+ class ClassCache
+ def initialize(class_names, config)
+ @class_names = class_names.stringify_keys
+ @config = config
+
+ # Remove string values that aren't constants or subclasses of AR
+ @class_names.delete_if { |k,klass|
+ unless klass.is_a? Class
+ klass = klass.safe_constantize
+ ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
+ end
+ !insert_class(@class_names, k, klass)
+ }
+ end
+
+ def [](fs_name)
+ @class_names.fetch(fs_name) {
+ klass = default_fixture_model(fs_name, @config).safe_constantize
+ insert_class(@class_names, fs_name, klass)
+ }
+ end
+
+ private
+
+ def insert_class(class_names, name, klass)
+ # We only want to deal with AR objects.
+ if klass && klass < ActiveRecord::Base
+ class_names[name] = klass
+ else
+ class_names[name] = nil
+ end
+ end
+
+ def default_fixture_model(fs_name, config)
+ ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config)
+ end
+ end
+
def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
fixture_set_names = Array(fixture_set_names).map(&:to_s)
- class_names = class_names.stringify_keys
+ class_names = ClassCache.new class_names, config
# FIXME: Apparently JK uses this.
connection = block_given? ? yield : ActiveRecord::Base.connection
@@ -452,10 +490,12 @@ module ActiveRecord
fixtures_map = {}
fixture_sets = files_to_read.map do |fs_name|
+ klass = class_names[fs_name]
+ conn = klass ? klass.connection : connection
fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
- connection,
+ conn,
fs_name,
- class_names[fs_name] || (default_fixture_model_name(fs_name, config).safe_constantize),
+ klass,
::File.join(fixtures_directory, fs_name))
end
@@ -500,10 +540,10 @@ module ActiveRecord
attr_reader :table_name, :name, :fixtures, :model_class, :config
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
- @fixtures = {} # Ordered hash
@name = name
@path = path
@config = config
+ @model_class = nil
if class_name.is_a?(String)
ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.")
@@ -515,14 +555,13 @@ module ActiveRecord
@model_class = class_name.safe_constantize if class_name
end
- @connection = ( model_class.respond_to?(:connection) ?
- model_class.connection : connection )
+ @connection = connection
@table_name = ( model_class.respond_to?(:table_name) ?
model_class.table_name :
self.class.default_fixture_table_name(name, config) )
- read_fixture_files
+ @fixtures = read_fixture_files path, @model_class
end
def [](x)
@@ -556,7 +595,7 @@ module ActiveRecord
rows[table_name] = fixtures.map do |label, fixture|
row = fixture.to_hash
- if model_class && model_class < ActiveRecord::Base
+ if model_class
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
if model_class.record_timestamps
timestamp_column_names.each do |c_name|
@@ -596,8 +635,10 @@ module ActiveRecord
row[fk_name] = ActiveRecord::FixtureSet.identify(value)
end
- when :has_and_belongs_to_many
- handle_habtm(rows, row, association)
+ when :has_many
+ if association.options[:through]
+ add_join_records(rows, row, HasManyThroughProxy.new(association))
+ end
end
end
end
@@ -607,18 +648,46 @@ module ActiveRecord
rows
end
+ class ReflectionProxy # :nodoc:
+ def initialize(association)
+ @association = association
+ end
+
+ def join_table
+ @association.join_table
+ end
+
+ def name
+ @association.name
+ end
+ end
+
+ class HasManyThroughProxy < ReflectionProxy # :nodoc:
+ def rhs_key
+ @association.foreign_key
+ end
+
+ def lhs_key
+ @association.through_reflection.foreign_key
+ end
+ end
+
private
def primary_key_name
@primary_key_name ||= model_class && model_class.primary_key
end
- def handle_habtm(rows, row, association)
+ def add_join_records(rows, row, association)
+ # This is the case when the join table has no fixtures file
if (targets = row.delete(association.name.to_s))
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
table_name = association.join_table
+ lhs_key = association.lhs_key
+ rhs_key = association.rhs_key
+
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
rows[table_name].concat targets.map { |target|
- { association.foreign_key => row[primary_key_name],
- association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) }
+ { lhs_key => row[primary_key_name],
+ rhs_key => ActiveRecord::FixtureSet.identify(target) }
}
end
end
@@ -641,12 +710,12 @@ module ActiveRecord
@column_names ||= @connection.columns(@table_name).collect { |c| c.name }
end
- def read_fixture_files
- yaml_files = Dir["#{@path}/{**,*}/*.yml"].select { |f|
+ def read_fixture_files(path, model_class)
+ yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
::File.file?(f)
- } + [yaml_file_path]
+ } + [yaml_file_path(path)]
- yaml_files.each do |file|
+ yaml_files.each_with_object({}) do |file, fixtures|
FixtureSet::File.open(file) do |fh|
fh.each do |fixture_name, row|
fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
@@ -655,8 +724,8 @@ module ActiveRecord
end
end
- def yaml_file_path
- "#{@path}.yml"
+ def yaml_file_path(path)
+ "#{path}.yml"
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index e826762def..7e1e120288 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -160,7 +160,7 @@ module ActiveRecord
end
def type_condition(table = arel_table)
- sti_column = table[inheritance_column.to_sym]
+ sti_column = table[inheritance_column]
sti_names = ([self] + descendants).map { |model| model.sti_name }
sti_column.in(sti_names)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 626fe40103..55776a91c0 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -150,6 +150,7 @@ module ActiveRecord
# Quote the column name used for optimistic locking.
def quoted_locking_column
+ ActiveSupport::Deprecation.warn "ActiveRecord::Base.quoted_locking_column is deprecated and will be removed in Rails 4.2 or later."
connection.quote_column_name(locking_column)
end
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index ddf2afca0c..ff7102d35b 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -3,12 +3,12 @@ module ActiveRecord
# Locking::Pessimistic provides support for row-level locking using
# SELECT ... FOR UPDATE and other lock types.
#
- # Pass <tt>lock: true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive
+ # Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
# lock on the selected rows:
# # select * from accounts where id=1 for update
- # Account.find(1, lock: true)
+ # Account.lock.find(1)
#
- # Pass <tt>lock: 'some locking clause'</tt> to give a database-specific locking clause
+ # Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
#
# Account.transaction do
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 0358a36b14..927fbab8f0 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -17,7 +17,7 @@ module ActiveRecord
def initialize
super
- @odd_or_even = false
+ @odd = false
end
def render_bind(column, value)
@@ -60,17 +60,8 @@ module ActiveRecord
debug " #{name} #{sql}#{binds}"
end
- def identity(event)
- return unless logger.debug?
-
- name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
- line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
-
- debug " #{name} #{line}"
- end
-
def odd?
- @odd_or_even = !@odd_or_even
+ @odd = !@odd
end
def logger
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index a1ad4f6255..27d398ad07 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -120,8 +120,8 @@ module ActiveRecord
# a column but keeps the type and content.
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
# the column to a different type using the same parameters as add_column.
- # * <tt>remove_column(table_name, column_names)</tt>: Removes the column listed in
- # +column_names+ from the table called +table_name+.
+ # * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
+ # named +column_name+ from the table called +table_name+.
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
# with the name of the column. Other options include
# <tt>:name</tt>, <tt>:unique</tt> (e.g.
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 75c0c1bda8..dc5ff02882 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -34,6 +34,12 @@ module ActiveRecord
##
# :singleton-method:
+ # Accessor for the name of the schema migrations table. By default, the value is "schema_migrations"
+ class_attribute :schema_migrations_table_name, instance_accessor: false
+ self.schema_migrations_table_name = "schema_migrations"
+
+ ##
+ # :singleton-method:
# Indicates whether table names should be the pluralized versions of the corresponding class names.
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
# See table_name for the full rules on table/class naming. This is true, by default.
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index d166f0dd66..080b20134d 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -6,7 +6,7 @@ module ActiveRecord
@records = []
end
- def pluck(_column_name)
+ def pluck(*column_names)
[]
end
@@ -42,10 +42,6 @@ module ActiveRecord
""
end
- def where_values_hash
- {}
- end
-
def count(*)
0
end
@@ -54,7 +50,7 @@ module ActiveRecord
0
end
- def calculate(_operation, _column_name, _options = {})
+ def calculate(_operation, _column_name)
if _operation == :count
0
else
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 582006ea7d..a73a140ef1 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -335,8 +335,18 @@ module ActiveRecord
# Reloads the record from the database.
#
- # This method modifies the receiver in-place. Attributes are updated, and
- # caches busted, in particular the associations cache.
+ # This method finds record by its primary key (which could be assigned manually) and
+ # modifies the receiver in-place:
+ #
+ # account = Account.new
+ # # => #<Account id: nil, email: nil>
+ # account.id = 1
+ # account.reload
+ # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
+ # # => #<Account id: 1, email: 'account@example.com'>
+ #
+ # Attributes are reloaded from the database, and caches busted, in
+ # particular the associations cache.
#
# If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
# is raised. Otherwise, in addition to the in-place modification the method
@@ -383,14 +393,16 @@ module ActiveRecord
end
@attributes.update(fresh_object.instance_variable_get('@attributes'))
- @columns_hash = fresh_object.instance_variable_get('@columns_hash')
- @attributes_cache = {}
+ @column_types = self.class.column_types
+ @column_types_override = fresh_object.instance_variable_get('@column_types_override')
+ @attributes_cache = {}
self
end
# Saves the record with the updated_at/on attributes set to the current time.
- # Please note that no validation is performed and no callbacks are executed.
+ # Please note that no validation is performed and only the +after_touch+
+ # callback is executed.
# If an attribute name is passed, that attribute is updated along with
# updated_at/on attributes.
#
@@ -433,7 +445,7 @@ module ActiveRecord
changes[self.class.locking_column] = increment_lock if locking_enabled?
- @changed_attributes.except!(*changes.keys)
+ changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 269f9f975b..eef08aea88 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -46,6 +46,7 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
+ ActiveRecord::Tasks::DatabaseTasks.root = Rails.root
if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
if engine.paths['db/migrate'].existent
diff --git a/activerecord/lib/active_record/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb
index 1a04950898..604a220303 100644
--- a/activerecord/lib/active_record/railties/console_sandbox.rb
+++ b/activerecord/lib/active_record/railties/console_sandbox.rb
@@ -1,7 +1,5 @@
ActiveRecord::Base.connection.begin_transaction(joinable: false)
at_exit do
- if ActiveRecord::Base.connection.transaction_open?
- ActiveRecord::Base.connection.rollback_transaction
- end
+ ActiveRecord::Base.connection.rollback_transaction
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index daccab762f..ecadb95a5d 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -287,6 +287,7 @@ db_namespace = namespace :db do
if ActiveRecord::Base.connection.supports_migrations?
File.open(filename, "a") do |f|
f.puts ActiveRecord::Base.connection.dump_schema_information
+ f.print "\n"
end
end
db_namespace['structure:dump'].reenable
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 5245fc130a..bce7766501 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -12,8 +12,6 @@ module ActiveRecord
def self.create(macro, name, scope, options, ar)
case macro
- when :has_and_belongs_to_many
- klass = AssociationReflection
when :has_many, :belongs_to, :has_one
klass = options[:through] ? ThroughReflection : AssociationReflection
when :composed_of
@@ -24,11 +22,11 @@ module ActiveRecord
end
def self.add_reflection(ar, name, reflection)
- if reflection.class == AggregateReflection
- ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection)
- else
- ar.reflections = ar.reflections.merge(name => reflection)
- end
+ ar.reflections = ar.reflections.merge(name => reflection)
+ end
+
+ def self.add_aggregate_reflection(ar, name, reflection)
+ ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection)
end
# \Reflection enables to interrogate Active Record classes and objects
@@ -121,6 +119,7 @@ module ActiveRecord
@scope = scope
@options = options
@active_record = active_record
+ @klass = options[:class]
@plural_name = active_record.pluralize_table_names ?
name.to_s.pluralize : name.to_s
end
@@ -195,10 +194,11 @@ module ActiveRecord
def initialize(macro, name, scope, options, active_record)
super
- @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
+ @collection = :has_many == macro
@automatic_inverse_of = nil
@type = options[:as] && "#{options[:as]}_type"
@foreign_type = options[:foreign_type] || "#{name}_type"
+ @constructable = calculate_constructable(macro, options)
end
# Returns a new, unsaved instance of the associated class. +attributes+ will
@@ -207,6 +207,10 @@ module ActiveRecord
klass.new(attributes, &block)
end
+ def constructable? # :nodoc:
+ @constructable
+ end
+
def table_name
klass.table_name
end
@@ -250,10 +254,6 @@ module ActiveRecord
def check_validity!
check_validity_of_inverse!
-
- if has_and_belongs_to_many? && association_foreign_key == foreign_key
- raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
- end
end
def check_validity_of_inverse!
@@ -335,10 +335,6 @@ module ActiveRecord
macro == :belongs_to
end
- def has_and_belongs_to_many?
- macro == :has_and_belongs_to_many
- end
-
def association_class
case macro
when :belongs_to
@@ -347,8 +343,6 @@ module ActiveRecord
else
Associations::BelongsToAssociation
end
- when :has_and_belongs_to_many
- Associations::HasAndBelongsToManyAssociation
when :has_many
if options[:through]
Associations::HasManyThroughAssociation
@@ -373,11 +367,23 @@ module ActiveRecord
protected
- def actual_source_reflection # FIXME: this is a horrible name
- self
- end
+ def actual_source_reflection # FIXME: this is a horrible name
+ self
+ end
private
+
+ def calculate_constructable(macro, options)
+ case macro
+ when :belongs_to
+ !options[:polymorphic]
+ when :has_one
+ !options[:through]
+ else
+ true
+ end
+ end
+
# Attempts to find the inverse association name automatically.
# If it cannot find a suitable inverse association name, it returns
# nil.
@@ -568,7 +574,7 @@ module ActiveRecord
# Add to it the scope from this reflection (if any)
scope_chain.first << scope if scope
- through_scope_chain = through_reflection.scope_chain
+ through_scope_chain = through_reflection.scope_chain.map(&:dup)
if options[:source_type]
through_scope_chain.first <<
@@ -587,7 +593,7 @@ module ActiveRecord
# A through association is nested if there would be more than one join table
def nested?
- chain.length > 2 || through_reflection.has_and_belongs_to_many?
+ chain.length > 2
end
# We want to use the klass from this reflection, rather than just delegate straight to
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 4e86e905ed..60f2726a6e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -244,8 +244,7 @@ module ActiveRecord
def empty?
return @records.empty? if loaded?
- c = count(:all)
- c.respond_to?(:zero?) ? c.zero? : c.empty?
+ limit_value == 0 ? true : !exists?
end
# Returns true if there are any records.
@@ -507,8 +506,7 @@ module ActiveRecord
visitor = connection.visitor
if eager_loading?
- join_dependency = construct_join_dependency
- relation = construct_relation_for_association_find(join_dependency)
+ find_with_associations { |rel| relation = rel }
end
ast = relation.arel.ast
@@ -599,8 +597,9 @@ module ActiveRecord
preload = preload_values
preload += includes_values unless eager_loading?
+ preloader = ActiveRecord::Associations::Preloader.new
preload.each do |associations|
- ActiveRecord::Associations::Preloader.new(@records, associations).run
+ preloader.preload @records, associations
end
@records.each { |record| record.readonly! } if readonly_value
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index fd8496442e..49b01909c6 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -34,7 +34,7 @@ module ActiveRecord
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
# (by setting the +:start+ option on that worker).
#
- # # Let's process for a batch of 2000 records, skiping the first 2000 rows
+ # # Let's process for a batch of 2000 records, skipping the first 2000 rows
# Person.find_each(start: 2000, batch_size: 2000) do |person|
# person.party_all_night!
# end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 27c04b0952..2d267183ce 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -19,17 +19,16 @@ module ActiveRecord
#
# Person.group(:city).count
# # => { 'Rome' => 5, 'Paris' => 3 }
- def count(column_name = nil, options = {})
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ def count(column_name = nil)
+ calculate(:count, column_name)
end
# Calculates the average value on a given column. Returns +nil+ if there's
# no row. See +calculate+ for examples with options.
#
# Person.average(:age) # => 35.8
- def average(column_name, options = {})
- calculate(:average, column_name, options)
+ def average(column_name)
+ calculate(:average, column_name)
end
# Calculates the minimum value on a given column. The value is returned
@@ -37,8 +36,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.minimum(:age) # => 7
- def minimum(column_name, options = {})
- calculate(:minimum, column_name, options)
+ def minimum(column_name)
+ calculate(:minimum, column_name)
end
# Calculates the maximum value on a given column. The value is returned
@@ -46,8 +45,8 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.maximum(:age) # => 93
- def maximum(column_name, options = {})
- calculate(:maximum, column_name, options)
+ def maximum(column_name)
+ calculate(:maximum, column_name)
end
# Calculates the sum of values on a given column. The value is returned
@@ -90,15 +89,15 @@ module ActiveRecord
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
- def calculate(operation, column_name, options = {})
+ def calculate(operation, column_name)
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
column_name = attribute_alias(column_name)
end
if has_include?(column_name)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
+ construct_relation_for_association_calculations.calculate(operation, column_name)
else
- perform_calculation(operation, column_name, options)
+ perform_calculation(operation, column_name)
end
end
@@ -181,7 +180,7 @@ module ActiveRecord
eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
end
- def perform_calculation(operation, column_name, options = {})
+ def perform_calculation(operation, column_name)
operation = operation.to_s.downcase
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index e28938f9d0..1e15bddcdf 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,4 +1,5 @@
require 'active_support/concern'
+require 'active_support/deprecation'
module ActiveRecord
module Delegation # :nodoc:
@@ -83,7 +84,7 @@ module ActiveRecord
if @klass.respond_to?(method)
self.class.delegate_to_scoped_klass(method)
scoping { @klass.send(method, *args, &block) }
- elsif Array.method_defined?(method)
+ elsif array_delegable?(method)
self.class.delegate method, :to => :to_a
to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
@@ -108,17 +109,27 @@ module ActiveRecord
end
def respond_to?(method, include_private = false)
- super || Array.method_defined?(method) ||
+ super || array_delegable?(method) ||
@klass.respond_to?(method, include_private) ||
arel.respond_to?(method, include_private)
end
protected
+ def array_delegable?(method)
+ defined = Array.method_defined?(method)
+ if defined && method.to_s.ends_with?('!')
+ ActiveSupport::Deprecation.warn(
+ "Association will no longer delegate #{method} to #to_a as of Rails 4.2. You instead must first call #to_a on the association to expose the array to be acted on."
+ )
+ end
+ defined
+ end
+
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
scoping { @klass.send(method, *args, &block) }
- elsif Array.method_defined?(method)
+ elsif array_delegable?(method)
to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
arel.send(method, *args, &block)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 0132a02f83..3a02bf90e9 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -201,7 +201,7 @@ module ActiveRecord
conditions = conditions.id if Base === conditions
return false if !conditions
- relation = construct_relation_for_association_find(construct_join_dependency)
+ relation = apply_join_dependency(self, construct_join_dependency)
return false if ActiveRecord::NullRelation === relation
relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
@@ -242,17 +242,25 @@ module ActiveRecord
def find_with_associations
join_dependency = construct_join_dependency
- relation = construct_relation_for_association_find(join_dependency)
- if ActiveRecord::NullRelation === relation
- []
+
+ aliases = join_dependency.aliases
+ relation = select aliases.columns
+ relation = apply_join_dependency(relation, join_dependency)
+
+ if block_given?
+ yield relation
else
- rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup)
- join_dependency.instantiate(rows)
+ if ActiveRecord::NullRelation === relation
+ []
+ else
+ rows = connection.select_all(relation.arel, 'SQL', relation.bind_values.dup)
+ join_dependency.instantiate(rows, aliases)
+ end
end
end
def construct_join_dependency(joins = [])
- including = (eager_load_values + includes_values).uniq
+ including = eager_load_values + includes_values
ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
end
@@ -260,14 +268,9 @@ module ActiveRecord
apply_join_dependency(self, construct_join_dependency(arel.froms.first))
end
- def construct_relation_for_association_find(join_dependency)
- relation = except(:select).select(join_dependency.columns)
- apply_join_dependency(relation, join_dependency)
- end
-
def apply_join_dependency(relation, join_dependency)
relation = relation.except(:includes, :eager_load, :preload)
- relation = join_dependency.join_relation(relation)
+ relation = relation.joins join_dependency
if using_limitable_reflections?(join_dependency.reflections)
relation
@@ -297,6 +300,8 @@ module ActiveRecord
protected
def find_with_ids(*ids)
+ raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
+
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 530c47d0d0..182b9ed89c 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -58,7 +58,11 @@ module ActiveRecord
def merge
normal_values.each do |name|
value = values[name]
- relation.send("#{name}!", *value) unless value.blank?
+ # The unless clause is here mostly for performance reasons (since the `send` call might be moderately
+ # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
+ # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
+ # don't fall through the cracks.
+ relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value)
end
merge_multi_values
@@ -90,7 +94,7 @@ module ActiveRecord
[])
relation.joins! rest
- @relation = join_dependency.join_relation(relation)
+ @relation = relation.joins join_dependency
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 9f2a039d94..bffd8b5d0f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -289,17 +289,7 @@ module ActiveRecord
end
def order!(*args) # :nodoc:
- args.flatten!
- validate_order_args(args)
-
- references = args.grep(String)
- references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
- references!(references) if references.any?
-
- # if a symbol is given we prepend the quoted table name
- args.map! do |arg|
- arg.is_a?(Symbol) ? Arel::Nodes::Ascending.new(klass.arel_table[arg]) : arg
- end
+ preprocess_order_args(args)
self.order_values += args
self
@@ -320,8 +310,7 @@ module ActiveRecord
end
def reorder!(*args) # :nodoc:
- args.flatten!
- validate_order_args(args)
+ preprocess_order_args(args)
self.reordering_value = true
self.order_values = args
@@ -867,7 +856,7 @@ module ActiveRecord
where_values.reject! do |rel|
case rel
- when Arel::Nodes::In, Arel::Nodes::Equality
+ when Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
subrelation.name.to_sym == target_value_sym
else
@@ -893,19 +882,25 @@ module ActiveRecord
end
def collapse_wheres(arel, wheres)
- equalities = wheres.grep(Arel::Nodes::Equality)
-
- arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
-
- (wheres - equalities).each do |where|
+ predicates = wheres.map do |where|
+ next where if ::Arel::Nodes::Equality === where
where = Arel.sql(where) if String === where
- arel.where(Arel::Nodes::Grouping.new(where))
+ Arel::Nodes::Grouping.new(where)
end
+
+ arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
end
def build_where(opts, other = [])
case opts
when String, Array
+ #TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
+ values = Hash === other.first ? other.first.values : other
+
+ values.grep(ActiveRecord::Relation) do |rel|
+ self.bind_values += rel.bind_values
+ end
+
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
@@ -926,6 +921,7 @@ module ActiveRecord
case opts
when Relation
name ||= 'subquery'
+ self.bind_values = opts.bind_values + self.bind_values
opts.arel.as(name.to_s)
else
opts
@@ -939,7 +935,7 @@ module ActiveRecord
:string_join
when Hash, Symbol, Array
:association_join
- when ActiveRecord::Associations::JoinDependency::JoinAssociation
+ when ActiveRecord::Associations::JoinDependency
:stashed_join
when Arel::Nodes::Join
:join_node
@@ -961,10 +957,7 @@ module ActiveRecord
join_list
)
- join_dependency.graft(*stashed_association_joins)
-
- joins = join_dependency.join_associations.map!(&:join_constraints)
- joins.flatten!
+ joins = join_dependency.join_constraints stashed_association_joins
joins.each { |join| manager.from(join) }
@@ -1036,6 +1029,20 @@ module ActiveRecord
end
end
+ def preprocess_order_args(order_args)
+ order_args.flatten!
+ validate_order_args(order_args)
+
+ references = order_args.grep(String)
+ references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
+ references!(references) if references.any?
+
+ # if a symbol is given we prepend the quoted table name
+ order_args.map! do |arg|
+ arg.is_a?(Symbol) ? Arel::Nodes::Ascending.new(klass.arel_table[arg]) : arg
+ end
+ end
+
# Checks to make sure that the arguments are not blank. Note that if some
# blank-like object were initially passed into the query method, then this
# method will not raise an error.
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 253368ae5b..1dc3bf3f12 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -46,6 +46,10 @@ module ActiveRecord
IDENTITY_TYPE
end
+ def column_type(name)
+ @column_types[name] || identity_type
+ end
+
def each
if block_given?
hash_rows.each { |row| yield row }
@@ -93,7 +97,21 @@ module ActiveRecord
# used as keys in ActiveRecord::Base's @attributes hash
columns = @columns.map { |c| c.dup.freeze }
@rows.map { |row|
- Hash[columns.zip(row)]
+ # In the past we used Hash[columns.zip(row)]
+ # though elegant, the verbose way is much more efficient
+ # both time and memory wise cause it avoids a big array allocation
+ # this method is called a lot and needs to be micro optimised
+ hash = {}
+
+ index = 0
+ length = columns.length
+
+ while index < length
+ hash[columns[index]] = row[index]
+ index += 1
+ end
+
+ hash
}
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 0b87ab9926..cab8fd745a 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -127,7 +127,17 @@ module ActiveRecord
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
bound = values.dup
c = connection
- statement.gsub('?') { quote_bound_value(bound.shift, c) }
+ statement.gsub('?') do
+ replace_bind_variable(bound.shift, c)
+ end
+ end
+
+ def replace_bind_variable(value, c = connection) #:nodoc:
+ if ActiveRecord::Relation === value
+ value.to_sql
+ else
+ quote_bound_value(value, c)
+ end
end
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
@@ -135,7 +145,7 @@ module ActiveRecord
if $1 == ':' # skip postgresql casts
$& # return the whole match
elsif bind_vars.include?(match = $2.to_sym)
- quote_bound_value(bind_vars[match])
+ replace_bind_variable(bind_vars[match])
else
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 1181cc739e..e055d571ab 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -17,9 +17,19 @@ module ActiveRecord
cattr_accessor :ignore_tables
@@ignore_tables = []
- def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
- new(connection).dump(stream)
- stream
+ class << self
+ def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base)
+ new(connection, generate_options(config)).dump(stream)
+ stream
+ end
+
+ private
+ def generate_options(config)
+ {
+ table_name_prefix: config.table_name_prefix,
+ table_name_suffix: config.table_name_suffix
+ }
+ end
end
def dump(stream)
@@ -32,10 +42,11 @@ module ActiveRecord
private
- def initialize(connection)
+ def initialize(connection, options = {})
@connection = connection
@types = @connection.native_database_types
@version = Migrator::current_version rescue nil
+ @options = options
end
def header(stream)
@@ -112,6 +123,7 @@ HEADER
tbl.print %Q(, primary_key: "#{pk}")
elsif pkcol.sql_type == 'uuid'
tbl.print ", id: :uuid"
+ tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
end
else
tbl.print ", id: false"
@@ -201,7 +213,7 @@ HEADER
end
def remove_prefix_and_suffix(table)
- table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
+ table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
end
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index fee19b1096..a9d164e366 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -7,11 +7,11 @@ module ActiveRecord
class << self
def table_name
- "#{table_name_prefix}schema_migrations#{table_name_suffix}"
+ "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
end
def index_name
- "#{table_name_prefix}unique_schema_migrations#{table_name_suffix}"
+ "#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
end
def table_exists?
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index a610f479f2..b841b977fc 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -86,6 +86,9 @@ module ActiveRecord
end
end
+ # assign new store attribute and create new hash to ensure that each class in the hierarchy
+ # has its own hash of stored attributes.
+ self.stored_attributes = {} if self.stored_attributes.blank?
self.stored_attributes[store_attribute] ||= []
self.stored_attributes[store_attribute] |= keys
end
@@ -101,26 +104,58 @@ module ActiveRecord
protected
def read_store_attribute(store_attribute, key)
- attribute = initialize_store_attribute(store_attribute)
- attribute[key]
+ accessor = store_accessor_for(store_attribute)
+ accessor.read(self, store_attribute, key)
end
def write_store_attribute(store_attribute, key, value)
- attribute = initialize_store_attribute(store_attribute)
- if value != attribute[key]
- send :"#{store_attribute}_will_change!"
- attribute[key] = value
- end
+ accessor = store_accessor_for(store_attribute)
+ accessor.write(self, store_attribute, key, value)
end
private
- def initialize_store_attribute(store_attribute)
- attribute = send(store_attribute)
- unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
- attribute = IndifferentCoder.as_indifferent_hash(attribute)
- send :"#{store_attribute}=", attribute
+ def store_accessor_for(store_attribute)
+ @column_types[store_attribute.to_s].accessor
+ end
+
+ class HashAccessor
+ def self.read(object, attribute, key)
+ prepare(object, attribute)
+ object.public_send(attribute)[key]
+ end
+
+ def self.write(object, attribute, key, value)
+ prepare(object, attribute)
+ if value != read(object, attribute, key)
+ object.public_send :"#{attribute}_will_change!"
+ object.public_send(attribute)[key] = value
+ end
+ end
+
+ def self.prepare(object, attribute)
+ object.public_send :"#{attribute}=", {} unless object.send(attribute)
+ end
+ end
+
+ class StringKeyedHashAccessor < HashAccessor
+ def self.read(object, attribute, key)
+ super object, attribute, key.to_s
+ end
+
+ def self.write(object, attribute, key, value)
+ super object, attribute, key.to_s, value
+ end
+ end
+
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor
+ def self.prepare(object, store_attribute)
+ attribute = object.send(store_attribute)
+ unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
+ attribute = IndifferentCoder.as_indifferent_hash(attribute)
+ object.send :"#{store_attribute}=", attribute
+ end
+ attribute
end
- attribute
end
class IndifferentCoder # :nodoc:
@@ -138,7 +173,7 @@ module ActiveRecord
end
def load(yaml)
- self.class.as_indifferent_hash @coder.load(yaml)
+ self.class.as_indifferent_hash(@coder.load(yaml))
end
def self.as_indifferent_hash(obj)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 5ff594fdca..b91bbeb412 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -23,6 +23,7 @@ module ActiveRecord
# * +fixtures_path+: a path to fixtures directory.
# * +migrations_paths+: a list of paths to directories with migrations.
# * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
+ # * +root+: a path to the root of the application.
#
# Example usage of +DatabaseTasks+ outside Rails could look as such:
#
@@ -37,7 +38,7 @@ module ActiveRecord
attr_writer :current_config
attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir,
- :fixtures_path, :env
+ :fixtures_path, :env, :root
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 50569d2462..c755831e6d 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -134,8 +134,9 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
args << "--password=#{configuration['password']}" if configuration['password']
args.concat(['--default-character-set', configuration['encoding']]) if configuration['encoding']
configuration.slice('host', 'port', 'socket').each do |k, v|
- args.concat([ "--#{k}", v ]) if v
+ args.concat([ "--#{k}", v.to_s ]) if v
end
+
args
end
end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 4413330fab..3d02ee07d0 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -59,7 +59,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- Kernel.system("psql -q -f #{filename} #{configuration['database']}")
+ Kernel.system("psql -q -f #{Shellwords.escape(filename)} #{configuration['database']}")
end
private
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index de8b16627e..5688931db2 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -3,7 +3,7 @@ module ActiveRecord
class SQLiteDatabaseTasks # :nodoc:
delegate :connection, :establish_connection, to: ActiveRecord::Base
- def initialize(configuration, root = Rails.root)
+ def initialize(configuration, root = ActiveRecord::Tasks::DatabaseTasks.root)
@configuration, @root = configuration, root
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index dcbf38a89f..45313b5e75 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -220,8 +220,8 @@ module ActiveRecord
# after_commit :do_bar, on: :update
# after_commit :do_baz, on: :destroy
#
- # after_commit :do_foo_bar, :on [:create, :update]
- # after_commit :do_bar_baz, :on [:update, :destroy]
+ # after_commit :do_foo_bar, on: [:create, :update]
+ # after_commit :do_bar_baz, on: [:update, :destroy]
#
# Note that transactional fixtures do not play well with this feature. Please
# use the +test_after_commit+ gem to have these hooks fired in tests.
diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb
index c8aa37f275..dc29213235 100644
--- a/activerecord/lib/rails/generators/active_record.rb
+++ b/activerecord/lib/rails/generators/active_record.rb
@@ -1,23 +1,17 @@
require 'rails/generators/named_base'
-require 'rails/generators/migration'
require 'rails/generators/active_model'
+require 'rails/generators/active_record/migration'
require 'active_record'
module ActiveRecord
module Generators # :nodoc:
class Base < Rails::Generators::NamedBase # :nodoc:
- include Rails::Generators::Migration
+ include ActiveRecord::Generators::Migration
# Set the current directory as base for the inherited generators.
def self.base_root
File.dirname(__FILE__)
end
-
- # Implement the required interface for Rails::Generators::Migration.
- def self.next_migration_number(dirname)
- next_migration_number = current_migration_number(dirname) + 1
- ActiveRecord::Migration.next_migration_number(next_migration_number)
- end
end
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
new file mode 100644
index 0000000000..b7418cf42f
--- /dev/null
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -0,0 +1,18 @@
+require 'rails/generators/migration'
+
+module ActiveRecord
+ module Generators # :nodoc:
+ module Migration
+ extend ActiveSupport::Concern
+ include Rails::Generators::Migration
+
+ module ClassMethods
+ # Implement the required interface for Rails::Generators::Migration.
+ def next_migration_number(dirname)
+ next_migration_number = current_migration_number(dirname) + 1
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index fedd9f603c..679c515e8c 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -3,14 +3,14 @@ require "cases/helper"
class MysqlConnectionTest < ActiveRecord::TestCase
def setup
super
+ @subscriber = SQLSubscriber.new
+ ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
@connection = ActiveRecord::Base.connection
- @connection.extend(LogIntercepter)
- @connection.intercepted = true
end
def teardown
- @connection.intercepted = false
- @connection.logged = []
+ ActiveSupport::Notifications.unsubscribe(@subscriber)
+ super
end
def test_no_automatic_reconnection_after_timeout
@@ -72,14 +72,14 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_logs_name_show_variable
@connection.show_variable 'foo'
- assert_equal "SCHEMA", @connection.logged[0][1]
+ assert_equal "SCHEMA", @subscriber.logged[0][1]
end
def test_logs_name_rename_column_sql
@connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
- @connection.logged = []
+ @subscriber.logged.clear
@connection.send(:rename_column_sql, 'bar_baz', 'foo', 'foo2')
- assert_equal "SCHEMA", @connection.logged[0][1]
+ assert_equal "SCHEMA", @subscriber.logged[0][1]
ensure
@connection.execute "DROP TABLE `bar_baz`"
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index ecdbefcd03..9536cceb1d 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -113,6 +113,11 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_equal(PgArray.last.tags, tag_values)
end
+ def test_attribute_for_inspect_for_array_field
+ record = PgArray.new { |a| a.ratings = (1..11).to_a }
+ assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings))
+ end
+
private
def assert_cycle field, array
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 6b726ce875..81aa977c59 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -7,14 +7,14 @@ module ActiveRecord
def setup
super
+ @subscriber = SQLSubscriber.new
+ ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
@connection = ActiveRecord::Base.connection
- @connection.extend(LogIntercepter)
- @connection.intercepted = true
end
def teardown
- @connection.intercepted = false
- @connection.logged = []
+ ActiveSupport::Notifications.unsubscribe(@subscriber)
+ super
end
def test_encoding
@@ -47,38 +47,48 @@ module ActiveRecord
def test_tables_logs_name
@connection.tables('hello')
- assert_equal 'SCHEMA', @connection.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
def test_indexes_logs_name
@connection.indexes('items', 'hello')
- assert_equal 'SCHEMA', @connection.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
def test_table_exists_logs_name
@connection.table_exists?('items')
- assert_equal 'SCHEMA', @connection.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
def test_table_alias_length_logs_name
@connection.instance_variable_set("@table_alias_length", nil)
@connection.table_alias_length
- assert_equal 'SCHEMA', @connection.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
def test_current_database_logs_name
@connection.current_database
- assert_equal 'SCHEMA', @connection.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
def test_encoding_logs_name
@connection.encoding
- assert_equal 'SCHEMA', @connection.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
def test_schema_names_logs_name
@connection.schema_names
- assert_equal 'SCHEMA', @connection.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
+ end
+
+ def test_statement_key_is_logged
+ bindval = 1
+ @connection.exec_query('SELECT $1::integer', 'SQL', [[nil, bindval]])
+ name = @subscriber.payloads.last[:statement_name]
+ assert name
+ res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(#{bindval})")
+ plan = res.column_types['QUERY PLAN'].type_cast res.rows.first.first
+ assert_operator plan.length, :>, 0
end
# Must have with_manual_interventions set to true for this
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 3dbab08a99..c5ff8cb609 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -589,38 +589,28 @@ _SQL
end
def test_timestamp_with_zone_values_with_rails_time_zone_support
- old_tz = ActiveRecord::Base.time_zone_aware_attributes
- old_default_tz = ActiveRecord::Base.default_timezone
+ with_timezone_config default: :utc, aware_attributes: true do
+ @connection.reconnect!
- ActiveRecord::Base.time_zone_aware_attributes = true
- ActiveRecord::Base.default_timezone = :utc
-
- @connection.reconnect!
-
- @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
- assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
- assert_instance_of Time, @first_timestamp_with_zone.time
+ @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
+ assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
+ assert_instance_of Time, @first_timestamp_with_zone.time
+ end
ensure
- ActiveRecord::Base.default_timezone = old_default_tz
- ActiveRecord::Base.time_zone_aware_attributes = old_tz
@connection.reconnect!
end
def test_timestamp_with_zone_values_without_rails_time_zone_support
- old_tz = ActiveRecord::Base.time_zone_aware_attributes
- old_default_tz = ActiveRecord::Base.default_timezone
-
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
-
- @connection.reconnect!
-
- @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
- assert_equal Time.local(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
- assert_instance_of Time, @first_timestamp_with_zone.time
+ with_timezone_config default: :local, aware_attributes: false do
+ @connection.reconnect!
+ # make sure to use a non-UTC time zone
+ @connection.execute("SET time zone 'America/Jamaica'", 'SCHEMA')
+
+ @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
+ assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
+ assert_instance_of Time, @first_timestamp_with_zone.time
+ end
ensure
- ActiveRecord::Base.default_timezone = old_default_tz
- ActiveRecord::Base.time_zone_aware_attributes = old_tz
@connection.reconnect!
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index f61f196c71..de724486c2 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -7,6 +7,8 @@ require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlHstoreTest < ActiveRecord::TestCase
class Hstore < ActiveRecord::Base
self.table_name = 'hstores'
+
+ store_accessor :settings, :language, :timezone
end
def setup
@@ -26,6 +28,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
@connection.transaction do
@connection.create_table('hstores') do |t|
t.hstore 'tags', :default => ''
+ t.hstore 'settings'
end
end
@column = Hstore.columns.find { |c| c.name == 'tags' }
@@ -90,6 +93,24 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b")))
end
+ def test_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
+
+ x.save!
+ x = Hstore.first
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
+
+ x.language = "de"
+ x.save!
+
+ x = Hstore.first
+ assert_equal "de", x.language
+ assert_equal "GMT", x.timezone
+ end
+
def test_gen1
assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''}))
end
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index adac1d3c13..c33c7ef968 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -7,6 +7,8 @@ require 'active_record/connection_adapters/postgresql_adapter'
class PostgresqlJSONTest < ActiveRecord::TestCase
class JsonDataType < ActiveRecord::Base
self.table_name = 'json_data_type'
+
+ store_accessor :settings, :resolution
end
def setup
@@ -15,6 +17,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
@connection.transaction do
@connection.create_table('json_data_type') do |t|
t.json 'payload', :default => {}
+ t.json 'settings'
end
end
rescue ActiveRecord::StatementInvalid
@@ -46,6 +49,13 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
JsonDataType.reset_column_information
end
+ def test_cast_value_on_write
+ x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar}
+ assert_equal({"string" => "foo", "symbol" => "bar"}, x.payload)
+ x.save
+ assert_equal({"string" => "foo", "symbol" => "bar"}, x.reload.payload)
+ end
+
def test_type_cast_json
assert @column
@@ -96,4 +106,19 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
x.payload = ['v1', {'k2' => 'v2'}, 'v3']
assert x.save!
end
+
+ def test_with_store_accessors
+ x = JsonDataType.new(resolution: "320×480")
+ assert_equal "320×480", x.resolution
+
+ x.save!
+ x = JsonDataType.first
+ assert_equal "320×480", x.resolution
+
+ x.resolution = "640×1136"
+ x.save!
+
+ x = JsonDataType.first
+ assert_equal "640×1136", x.resolution
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
index f1c4b85126..c5fd40accc 100644
--- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
@@ -1,38 +1,40 @@
require 'cases/helper'
-module ActiveRecord::ConnectionAdapters
- class PostgreSQLAdapter < AbstractAdapter
- class InactivePGconn
- def query(*args)
- raise PGError
- end
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter < AbstractAdapter
+ class InactivePGconn
+ def query(*args)
+ raise PGError
+ end
- def status
- PGconn::CONNECTION_BAD
+ def status
+ PGconn::CONNECTION_BAD
+ end
end
- end
- class StatementPoolTest < ActiveRecord::TestCase
- def test_cache_is_per_pid
- return skip('must support fork') unless Process.respond_to?(:fork)
+ class StatementPoolTest < ActiveRecord::TestCase
+ def test_cache_is_per_pid
+ return skip('must support fork') unless Process.respond_to?(:fork)
- cache = StatementPool.new nil, 10
- cache['foo'] = 'bar'
- assert_equal 'bar', cache['foo']
+ cache = StatementPool.new nil, 10
+ cache['foo'] = 'bar'
+ assert_equal 'bar', cache['foo']
- pid = fork {
- lookup = cache['foo'];
- exit!(!lookup)
- }
+ pid = fork {
+ lookup = cache['foo'];
+ exit!(!lookup)
+ }
- Process.waitpid pid
- assert $?.success?, 'process should exit successfully'
- end
+ Process.waitpid pid
+ assert $?.success?, 'process should exit successfully'
+ end
- def test_dealloc_does_not_raise_on_inactive_connection
- cache = StatementPool.new InactivePGconn.new, 10
- cache['foo'] = 'bar'
- assert_nothing_raised { cache.clear }
+ def test_dealloc_does_not_raise_on_inactive_connection
+ cache = StatementPool.new InactivePGconn.new, 10
+ cache['foo'] = 'bar'
+ assert_nothing_raised { cache.clear }
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index b573d48807..a753a23c09 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -24,7 +24,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
@connection.reconnect!
@connection.transaction do
- @connection.create_table('pg_uuids', id: :uuid) do |t|
+ @connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
t.string 'name'
t.uuid 'other_uuid', default: 'uuid_generate_v4()'
end
@@ -60,7 +60,8 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
def test_schema_dumper_for_uuid_primary_key
schema = StringIO.new
ActiveRecord::SchemaDumper.dump(@connection, schema)
- assert_match(/\bcreate_table "pg_uuids", id: :uuid\b/, schema.string)
+ 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
end
@@ -93,3 +94,43 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
assert_nil col_desc["default"]
end
end
+
+class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
+ class UuidPost < ActiveRecord::Base
+ self.table_name = 'pg_uuid_posts'
+ has_many :uuid_comments, inverse_of: :uuid_post
+ end
+
+ class UuidComment < ActiveRecord::Base
+ self.table_name = 'pg_uuid_comments'
+ belongs_to :uuid_post
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.reconnect!
+
+ @connection.transaction do
+ @connection.create_table('pg_uuid_posts', id: :uuid) do |t|
+ t.string 'title'
+ end
+ @connection.create_table('pg_uuid_comments', id: :uuid) do |t|
+ t.uuid :uuid_post_id, default: 'uuid_generate_v4()'
+ t.string 'content'
+ end
+ end
+ end
+
+ def teardown
+ @connection.transaction do
+ @connection.execute 'drop table if exists pg_uuid_comments'
+ @connection.execute 'drop table if exists pg_uuid_posts'
+ end
+ end
+
+ def test_collection_association_with_uuid
+ post = UuidPost.create!
+ comment = post.uuid_comments.create!
+ assert post.uuid_comments.find(comment.id)
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 6ba6518eaa..ce7c869eec 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -21,8 +21,8 @@ module ActiveRecord
)
eosql
- @conn.extend(LogIntercepter)
- @conn.intercepted = true
+ @subscriber = SQLSubscriber.new
+ ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
def test_valid_column
@@ -31,16 +31,16 @@ module ActiveRecord
end
# sqlite databases should be able to support any type and not
- # just the ones mentioned in the native_database_types.
- # Therefore test_invalid column should always return true
+ # just the ones mentioned in the native_database_types.
+ # Therefore test_invalid column should always return true
# even if the type is not valid.
def test_invalid_column
assert @conn.valid_type?(:foobar)
end
def teardown
- @conn.intercepted = false
- @conn.logged = []
+ ActiveSupport::Notifications.unsubscribe(@subscriber)
+ super
end
def test_column_types
@@ -256,7 +256,7 @@ module ActiveRecord
def test_tables_logs_name
assert_logged [['SCHEMA', []]] do
@conn.tables('hello')
- assert_not_nil @conn.logged.first.shift
+ assert_not_nil @subscriber.logged.first.shift
end
end
@@ -268,7 +268,7 @@ module ActiveRecord
def test_table_exists_logs_name
assert @conn.table_exists?('items')
- assert_equal 'SCHEMA', @conn.logged[0][1]
+ assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
def test_columns
@@ -306,10 +306,10 @@ module ActiveRecord
end
def test_indexes_logs
- assert_difference('@conn.logged.length') do
+ assert_difference('@subscriber.logged.length') do
@conn.indexes('items')
end
- assert_match(/items/, @conn.logged.last.first)
+ assert_match(/items/, @subscriber.logged.last.first)
end
def test_no_indexes
@@ -370,7 +370,7 @@ module ActiveRecord
def assert_logged logs
yield
- assert_equal logs, @conn.logged
+ assert_equal logs, @subscriber.logged
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 28bf48f4fd..498a4e8144 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -4,6 +4,7 @@ require 'models/tagging'
require 'models/tag'
require 'models/comment'
require 'models/author'
+require 'models/essay'
require 'models/category'
require 'models/company'
require 'models/person'
@@ -24,7 +25,7 @@ require 'models/categorization'
require 'models/sponsor'
class EagerAssociationTest < ActiveRecord::TestCase
- fixtures :posts, :comments, :authors, :author_addresses, :categories, :categories_posts,
+ fixtures :posts, :comments, :authors, :essays, :author_addresses, :categories, :categories_posts,
:companies, :accounts, :tags, :taggings, :people, :readers, :categorizations,
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
:developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors
@@ -747,6 +748,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_default_scope_as_block
+ # warm up the habtm cache
+ EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first.projects
developer = EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first
projects = Project.order(:id).to_a
assert_no_queries do
@@ -1136,6 +1139,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_deep_including_through_habtm
+ # warm up habtm cache
+ posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a
+ posts[0].categories[0].categorizations.length
+
posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a
assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length }
assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length }
@@ -1172,8 +1179,19 @@ class EagerAssociationTest < ActiveRecord::TestCase
}
end
- test "works in combination with order(:symbol)" do
- author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first
+ test "works in combination with order(:symbol) and reorder(:symbol)" do
+ author = Author.includes(:posts).references(:posts).order(:name).find_by('posts.title IS NOT NULL')
assert_equal authors(:bob), author
+
+ author = Author.includes(:posts).references(:posts).reorder(:name).find_by('posts.title IS NOT NULL')
+ assert_equal authors(:bob), author
+ end
+
+ test "preloading with a polymorphic association and using the existential predicate" do
+ assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer
+
+ assert_nothing_raised do
+ authors(:david).essays.includes(:writer).any?
+ end
end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 47dff7d0ea..f8f2832ab1 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -75,7 +75,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
private
def extend!(model)
- builder = ActiveRecord::Associations::Builder::HasMany.new(:association_name, nil, {}) { }
- builder.define_extensions(model)
+ ActiveRecord::Associations::Builder::HasMany.define_extensions(model, :association_name) { }
end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 712a770133..be928ec8ee 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -605,16 +605,24 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_join_table_alias
+ # FIXME: `references` has no impact on the aliases generated for the join
+ # query. The fact that we pass `:developers_projects_join` to `references`
+ # and that the SQL string contains `developers_projects_join` is merely a
+ # coincidence.
assert_equal(
3,
Developer.references(:developers_projects_join).merge(
:includes => {:projects => :developers},
- :where => 'developers_projects_join.joined_on IS NOT NULL'
+ :where => 'projects_developers_projects_join.joined_on IS NOT NULL'
).to_a.size
)
end
def test_join_with_group
+ # FIXME: `references` has no impact on the aliases generated for the join
+ # query. The fact that we pass `:developers_projects_join` to `references`
+ # and that the SQL string contains `developers_projects_join` is merely a
+ # coincidence.
group = Developer.columns.inject([]) do |g, c|
g << "developers.#{c.name}"
g << "developers_projects_2.#{c.name}"
@@ -624,7 +632,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal(
3,
Developer.references(:developers_projects_join).merge(
- :includes => {:projects => :developers}, :where => 'developers_projects_join.joined_on IS NOT NULL',
+ :includes => {:projects => :developers}, :where => 'projects_developers_projects_join.joined_on IS NOT NULL',
:group => group.join(",")
).to_a.size
)
@@ -638,12 +646,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_scoped_grouped
- assert_equal 5, categories(:general).posts_grouped_by_title.size
- assert_equal 1, categories(:technology).posts_grouped_by_title.size
+ assert_equal 5, categories(:general).posts_grouped_by_title.to_a.size
+ assert_equal 1, categories(:technology).posts_grouped_by_title.to_a.size
end
def test_find_scoped_grouped_having
- assert_equal 2, projects(:active_record).well_payed_salary_groups.size
+ assert_equal 2, projects(:active_record).well_payed_salary_groups.to_a.size
assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 }
end
@@ -710,12 +718,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal project, developer.projects.first
end
- def test_self_referential_habtm_without_foreign_key_set_should_raise_exception
- assert_raise(ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded) {
- SelfMember.new.friends
- }
- end
-
def test_dynamic_find_should_respect_association_include
# SQL error in sort clause if :include is not included
# due to Unknown column 'authors.id'
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ce51853bf3..8bdbd84ad1 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -45,6 +45,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.destroyed_client_ids.clear
end
+ def test_anonymous_has_many
+ developer = Class.new(ActiveRecord::Base) {
+ self.table_name = 'developers'
+ dev = self
+
+ developer_project = Class.new(ActiveRecord::Base) {
+ self.table_name = 'developers_projects'
+ belongs_to :developer, :class => dev
+ }
+ has_many :developer_projects, :class => developer_project, :foreign_key => 'developer_id'
+ }
+ dev = developer.first
+ named = Developer.find(dev.id)
+ assert_operator dev.developer_projects.count, :>, 0
+ assert_equal named.projects.map(&:id).sort,
+ dev.developer_projects.map(&:project_id).sort
+ end
+
def test_create_from_association_should_respect_default_scope
car = Car.create(:name => 'honda')
assert_equal 'honda', car.name
@@ -62,6 +80,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'exotic', bulb.name
end
+ def test_build_from_association_should_respect_scope
+ author = Author.new
+
+ post = author.thinking_posts.build
+ assert_equal 'So I was thinking', post.title
+ end
+
def test_create_from_association_with_nil_values_should_work
car = Car.create(:name => 'honda')
@@ -319,6 +344,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) }
end
+ def test_find_ids_and_inverse_of
+ force_signal37_to_load_all_clients_of_firm
+
+ firm = companies(:first_firm)
+ client = firm.clients_of_firm.find(3)
+ assert_kind_of Client, client
+
+ client_ary = firm.clients_of_firm.find([3])
+ assert_kind_of Array, client_ary
+ assert_equal client, client_ary.first
+ end
+
def test_find_all
firm = Firm.all.merge!(:order => "id").first
assert_equal 2, firm.clients.where("#{QUOTED_TYPE} = 'Client'").to_a.length
@@ -500,6 +537,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_inverse_on_before_validate
+ firm = companies(:first_firm)
+ assert_queries(1) do
+ firm.clients_of_firm << Client.new("name" => "Natural Company")
+ end
+ end
+
def test_new_aliased_to_build
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") }
@@ -741,10 +785,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
clients = companies(:first_firm).clients_of_firm.to_a
assert_equal 2, clients.count
- deleted = companies(:first_firm).clients_of_firm.delete_all
- assert_equal clients.sort_by(&:id), deleted.sort_by(&:id)
- assert_equal 0, companies(:first_firm).clients_of_firm.size
- assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+
+ assert_difference "Client.count", -(clients.count) do
+ companies(:first_firm).clients_of_firm.delete_all
+ end
end
def test_delete_all_with_not_yet_loaded_association_collection
@@ -1396,15 +1440,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_calling_first_or_last_with_integer_on_association_should_load_association
+ def test_calling_first_or_last_with_integer_on_association_should_not_load_association
firm = companies(:first_firm)
+ firm.clients.create(:name => 'Foo')
+ assert !firm.clients.loaded?
- assert_queries 1 do
+ assert_queries 2 do
firm.clients.first(2)
firm.clients.last(2)
end
- assert firm.clients.loaded?
+ assert !firm.clients.loaded?
end
def test_calling_many_should_count_instead_of_loading_association
@@ -1590,6 +1636,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.attributes_after_initialize['car_id']
end
+ def test_attributes_are_set_when_initialized_from_has_many_null_relationship
+ car = Car.new name: 'honda'
+ bulb = car.bulbs.where(name: 'headlight').first_or_initialize
+ assert_equal 'headlight', bulb.name
+ end
+
+ def test_attributes_are_set_when_initialized_from_polymorphic_has_many_null_relationship
+ post = Post.new title: 'title', body: 'bar'
+ tag = Tag.create!(name: 'foo')
+
+ tagging = post.taggings.where(tag: tag).first_or_initialize
+
+ assert_equal tag.id, tagging.tag_id
+ assert_equal 'Post', tagging.taggable_type
+ end
+
def test_replace
car = Car.create(:name => 'honda')
bulb1 = car.bulbs.create
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index c00cae5750..c450b1beb5 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -29,7 +29,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
:owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses,
:subscribers, :books, :subscriptions, :developers, :categorizations, :essays,
- :categories_posts
+ :categories_posts, :clubs, :memberships
# Dummies to force column loads so query counts are clean.
def setup
@@ -37,6 +37,128 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Reader.create :person_id => 0, :post_id => 0
end
+ def test_preload_sti_rhs_class
+ developers = Developer.includes(:firms).all.to_a
+ assert_no_queries do
+ developers.each { |d| d.firms }
+ end
+ end
+
+ def test_preload_sti_middle_relation
+ club = Club.create!(name: 'Aaron cool banana club')
+ member1 = Member.create!(name: 'Aaron')
+ member2 = Member.create!(name: 'Cat')
+
+ SuperMembership.create! club: club, member: member1
+ CurrentMembership.create! club: club, member: member2
+
+ club1 = Club.includes(:members).find_by_id club.id
+ assert_equal [member1, member2].sort_by(&:id),
+ club1.members.sort_by(&:id)
+ end
+
+ def make_model(name)
+ Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
+ end
+
+ def test_ordered_habtm
+ person_prime = Class.new(ActiveRecord::Base) do
+ def self.name; 'Person'; end
+
+ has_many :readers
+ has_many :posts, -> { order('posts.id DESC') }, :through => :readers
+ end
+ posts = person_prime.includes(:posts).first.posts
+
+ assert_operator posts.length, :>, 1
+ posts.each_cons(2) do |left,right|
+ assert_operator left.id, :>, right.id
+ end
+ end
+
+ def test_singleton_has_many_through
+ book = make_model "Book"
+ subscription = make_model "Subscription"
+ subscriber = make_model "Subscriber"
+
+ subscriber.primary_key = 'nick'
+ subscription.belongs_to :book, class: book
+ subscription.belongs_to :subscriber, class: subscriber
+
+ book.has_many :subscriptions, class: subscription
+ book.has_many :subscribers, through: :subscriptions, class: subscriber
+
+ anonbook = book.first
+ namebook = Book.find anonbook.id
+
+ assert_operator anonbook.subscribers.count, :>, 0
+ anonbook.subscribers.each do |s|
+ assert_instance_of subscriber, s
+ end
+ assert_equal namebook.subscribers.map(&:id).sort,
+ anonbook.subscribers.map(&:id).sort
+ end
+
+ def test_no_pk_join_table_append
+ lesson, _, student = make_no_pk_hm_t
+
+ sicp = lesson.new(:name => "SICP")
+ ben = student.new(:name => "Ben Bitdiddle")
+ sicp.students << ben
+ assert sicp.save!
+ end
+
+ def test_no_pk_join_table_delete
+ lesson, lesson_student, student = make_no_pk_hm_t
+
+ sicp = lesson.new(:name => "SICP")
+ ben = student.new(:name => "Ben Bitdiddle")
+ louis = student.new(:name => "Louis Reasoner")
+ sicp.students << ben
+ sicp.students << louis
+ assert sicp.save!
+
+ sicp.students.reload
+ assert_operator lesson_student.count, :>=, 2
+ assert_no_difference('student.count') do
+ assert_difference('lesson_student.count', -2) do
+ sicp.students.destroy(*student.all.to_a)
+ end
+ end
+ end
+
+ def test_no_pk_join_model_callbacks
+ lesson, lesson_student, student = make_no_pk_hm_t
+
+ after_destroy_called = false
+ lesson_student.after_destroy do
+ after_destroy_called = true
+ end
+
+ sicp = lesson.new(:name => "SICP")
+ ben = student.new(:name => "Ben Bitdiddle")
+ sicp.students << ben
+ assert sicp.save!
+
+ sicp.students.reload
+ sicp.students.destroy(*student.all.to_a)
+ assert after_destroy_called, "after destroy should be called"
+ end
+
+ def make_no_pk_hm_t
+ lesson = make_model 'Lesson'
+ student = make_model 'Student'
+
+ lesson_student = make_model 'LessonStudent'
+ lesson_student.table_name = 'lessons_students'
+
+ lesson_student.belongs_to :lesson, :class => lesson
+ lesson_student.belongs_to :student, :class => student
+ lesson.has_many :lesson_students, :class => lesson_student
+ lesson.has_many :students, :through => :lesson_students, :class => student
+ [lesson, lesson_student, student]
+ end
+
def test_pk_is_not_required_for_join
post = Post.includes(:scategories).first
post2 = Post.includes(:categories).first
@@ -663,7 +785,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1)
john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1)
assert_equal sarah.agents, [john]
- assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents
+ assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents
end
def test_associate_existing_with_nonstandard_primary_key_on_belongs_to
@@ -963,4 +1085,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
readers(:michael_authorless).update(first_post_id: 1)
assert_equal [posts(:thinking)], person.reload.first_posts
end
+
+ def test_has_many_through_with_includes_in_through_association_scope
+ assert_not_empty posts(:welcome).author_address_extra_with_address
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 4fdf9a9643..cdd386187b 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -505,6 +505,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_no_queries { company.account = nil }
account = Account.find(2)
assert_queries { company.account = account }
+
+ assert_no_queries { Firm.new.account = account }
end
def test_has_one_assignment_triggers_save_on_change
@@ -522,4 +524,15 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal 'new name', pirate.ship.reload.name
end
+ def test_has_one_autosave_with_primary_key_manually_set
+ post = Post.create(id: 1234, title: "Some title", body: 'Some content')
+ author = Author.new(id: 33, name: 'Hank Moody')
+
+ author.post = post
+ author.save
+ author.reload
+
+ assert_not_nil author.post
+ assert_equal author.post, post
+ end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index de47a576c6..9fe5ff50d9 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -41,6 +41,11 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_no_match(/WHERE/i, sql)
end
+ def test_join_association_conditions_support_string_and_arel_expressions
+ assert_equal 0, Author.joins(:welcome_posts_with_comment).count
+ assert_equal 1, Author.joins(:welcome_posts_with_comments).count
+ end
+
def test_join_conditions_allow_nil_associations
authors = Author.includes(:essays).where(:essays => {:id => nil})
assert_equal 2, authors.count
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 2477e60e51..893030345f 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -446,6 +446,19 @@ class InverseHasManyTests < ActiveRecord::TestCase
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
+
+ def test_child_instance_should_point_to_parent_without_saving
+ man = Man.new
+ i = Interest.create(:topic => 'Industrial Revolution Re-enactment')
+
+ man.interests << i
+ assert_not_nil i.man
+
+ i.man.name = "Charles"
+ assert_equal i.man.name, man.name
+
+ assert !man.persisted?
+ end
end
class InverseBelongsToTests < ActiveRecord::TestCase
@@ -590,6 +603,18 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
end
+ def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed
+ new_man = Man.new
+ face = Face.new
+ new_man.face = face
+
+ old_inversed_man = face.man
+ new_man.save!
+ new_inversed_man = face.man
+
+ assert_equal old_inversed_man.object_id, new_inversed_man.object_id
+ end
+
def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
i = interests(:llama_wrangling)
m = i.polymorphic_man
diff --git a/activerecord/test/cases/associations/join_dependency_test.rb b/activerecord/test/cases/associations/join_dependency_test.rb
deleted file mode 100644
index 08c166dc33..0000000000
--- a/activerecord/test/cases/associations/join_dependency_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require "cases/helper"
-require 'models/edge'
-
-class JoinDependencyTest < ActiveRecord::TestCase
- def test_column_names_with_alias_handles_nil_primary_key
- assert_equal Edge.column_names, ActiveRecord::Associations::JoinDependency::JoinBase.new(Edge).column_names_with_alias.map(&:first)
- end
-end \ No newline at end of file
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index cf3c07845c..8ef351cda8 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -214,7 +214,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload
- authors = assert_queries(3) { Author.includes(:post_categories).to_a.sort_by(&:id) }
+ authors = assert_queries(4) { Author.includes(:post_categories).to_a.sort_by(&:id) }
general, cooking = categories(:general), categories(:cooking)
assert_no_queries do
@@ -242,7 +242,8 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload
- categories = assert_queries(3) { Category.includes(:post_comments).to_a.sort_by(&:id) }
+ Category.includes(:post_comments).to_a # preheat cache
+ categories = assert_queries(4) { Category.includes(:post_comments).to_a.sort_by(&:id) }
greetings, more = comments(:greetings), comments(:more_greetings)
assert_no_queries do
@@ -270,7 +271,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload
- authors = assert_queries(5) { Author.includes(:category_post_comments).to_a.sort_by(&:id) }
+ authors = assert_queries(6) { Author.includes(:category_post_comments).to_a.sort_by(&:id) }
greetings, more = comments(:greetings), comments(:more_greetings)
assert_no_queries do
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index baba55ea43..9c66ed354e 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -92,7 +92,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_set_attributes_without_hash
topic = Topic.new
- assert_nothing_raised { topic.attributes = '' }
+ assert_raise(ArgumentError) { topic.attributes = '' }
end
def test_integers_as_nil
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 635278abc1..517d2674a7 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -1440,10 +1440,6 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
test "should generate validation methods for HABTM associations with :validate => true" do
assert_respond_to @pirate, :validate_associated_records_for_parrots
end
-
- test "should not generate validation methods for HABTM associations without :validate => true" do
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrots)
- end
end
class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index dc2de7b523..82b20e8cee 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -78,12 +78,6 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
- def setup
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
- Time.zone = nil
- end
-
def test_generated_methods_modules
modules = Computer.ancestors
assert modules.include?(Computer::GeneratedFeatureMethods)
@@ -234,7 +228,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :utc do
+ with_timezone_config default: :utc do
time = Time.local(2000)
topic = Topic.create('written_on' => time)
saved_time = Topic.find(topic.id).reload.written_on
@@ -247,7 +241,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :utc do
+ with_timezone_config default: :utc do
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
@@ -262,18 +256,20 @@ class BasicsTest < ActiveRecord::TestCase
def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local
with_env_tz 'America/New_York' do
- time = Time.utc(2000)
- topic = Topic.create('written_on' => time)
- saved_time = Topic.find(topic.id).reload.written_on
- assert_equal time, saved_time
- assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
- assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
+ with_timezone_config default: :local do
+ time = Time.utc(2000)
+ topic = Topic.create('written_on' => time)
+ saved_time = Topic.find(topic.id).reload.written_on
+ assert_equal time, saved_time
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
+ assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
+ end
end
end
def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :local do
+ with_timezone_config default: :local do
Time.use_zone 'Central Time (US & Canada)' do
time = Time.zone.local(2000)
topic = Topic.create('written_on' => time)
@@ -493,25 +489,25 @@ class BasicsTest < ActiveRecord::TestCase
# Oracle, and Sybase do not have a TIME datatype.
unless current_adapter?(:OracleAdapter, :SybaseAdapter)
def test_utc_as_time_zone
- Topic.default_timezone = :utc
- attributes = { "bonus_time" => "5:42:00AM" }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
- Topic.default_timezone = :local
+ with_timezone_config default: :utc do
+ attributes = { "bonus_time" => "5:42:00AM" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ end
end
def test_utc_as_time_zone_and_new
- Topic.default_timezone = :utc
- attributes = { "bonus_time(1i)"=>"2000",
- "bonus_time(2i)"=>"1",
- "bonus_time(3i)"=>"1",
- "bonus_time(4i)"=>"10",
- "bonus_time(5i)"=>"35",
- "bonus_time(6i)"=>"50" }
- topic = Topic.new(attributes)
- assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
- Topic.default_timezone = :local
+ with_timezone_config default: :utc do
+ attributes = { "bonus_time(1i)"=>"2000",
+ "bonus_time(2i)"=>"1",
+ "bonus_time(3i)"=>"1",
+ "bonus_time(4i)"=>"10",
+ "bonus_time(5i)"=>"35",
+ "bonus_time(6i)"=>"50" }
+ topic = Topic.new(attributes)
+ assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
+ end
end
end
@@ -558,11 +554,27 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
- def test_comparison
- topic_1 = Topic.create!
- topic_2 = Topic.create!
+ def test_create_without_prepared_statement
+ topic = Topic.connection.unprepared_statement do
+ Topic.create(:title => 'foo')
+ end
+
+ assert_equal topic, Topic.find(topic.id)
+ end
+
+ def test_destroy_without_prepared_statement
+ topic = Topic.create(title: 'foo')
+ Topic.connection.unprepared_statement do
+ Topic.find(topic.id).destroy
+ end
+
+ assert_equal nil, Topic.find_by_id(topic.id)
+ end
- assert_equal [topic_2, topic_1].sort, [topic_1, topic_2]
+ def test_blank_ids
+ one = Subscriber.new(:id => '')
+ two = Subscriber.new(:id => '')
+ assert_equal one, two
end
def test_comparison_with_different_objects
@@ -571,6 +583,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil topic <=> category
end
+ def test_comparison_with_different_objects_in_array
+ topic = Topic.create
+ assert_raises(ArgumentError) do
+ [1, topic].sort
+ end
+ end
+
def test_readonly_attributes
assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
@@ -585,10 +604,25 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_unicode_column_name
+ Weird.reset_column_information
weird = Weird.create(:なまえ => 'たこ焼き仮面')
assert_equal 'たこ焼き仮面', weird.なまえ
end
+ def test_respect_internal_encoding
+ if current_adapter?(:PostgreSQLAdapter)
+ skip 'pg does not respect internal encoding and always returns utf8'
+ end
+ old_default_internal = Encoding.default_internal
+ silence_warnings { Encoding.default_internal = "EUC-JP" }
+
+ Weird.reset_column_information
+
+ assert_equal ["EUC-JP"], Weird.columns.map {|c| c.name.encoding.name }.uniq
+ ensure
+ silence_warnings { Encoding.default_internal = old_default_internal }
+ end
+
def test_non_valid_identifier_column_name
weird = Weird.create('a$b' => 'value')
weird.reload
@@ -611,12 +645,14 @@ class BasicsTest < ActiveRecord::TestCase
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
- attributes = {
- "bonus_time" => "5:42:00AM"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ with_timezone_config default: :local do
+ attributes = {
+ "bonus_time" => "5:42:00AM"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ end
end
def test_attributes_on_dummy_time_with_invalid_time
@@ -804,19 +840,18 @@ class BasicsTest < ActiveRecord::TestCase
# TODO: extend defaults tests to other databases!
if current_adapter?(:PostgreSQLAdapter)
def test_default
- tz = Default.default_timezone
- Default.default_timezone = :local
- default = Default.new
- Default.default_timezone = tz
-
- # fixed dates / times
- assert_equal Date.new(2004, 1, 1), default.fixed_date
- assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
-
- # char types
- assert_equal 'Y', default.char1
- assert_equal 'a varchar field', default.char2
- assert_equal 'a text field', default.char3
+ with_timezone_config default: :local do
+ default = Default.new
+
+ # fixed dates / times
+ assert_equal Date.new(2004, 1, 1), default.fixed_date
+ assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
+
+ # char types
+ assert_equal 'Y', default.char1
+ assert_equal 'a varchar field', default.char2
+ assert_equal 'a text field', default.char3
+ end
end
class Geometric < ActiveRecord::Base; end
@@ -1325,6 +1360,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_marshal_between_processes
+ skip "can't marshal between processes when using an in-memory db" if in_memory_db?
skip "fork isn't supported" unless Process.respond_to?(:fork)
# Define a new model to ensure there are no caches
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index 3a4f414ae8..2a6d8cc2ab 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -110,6 +110,14 @@ module ActiveRecord
assert_equal 1800, column.type_cast(30.minutes)
assert_equal 7200, column.type_cast(2.hours)
end
+
+ def test_string_to_time_with_timezone
+ [:utc, :local].each do |zone|
+ with_timezone_config default: zone do
+ assert_equal Time.utc(2013, 9, 4, 0, 0, 0), Column.string_to_time("Wed, 04 Sep 2013 03:00:00 EAT")
+ end
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index 427076bd80..c0491bbee5 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -5,7 +5,7 @@ require 'models/task'
class DateTimeTest < ActiveRecord::TestCase
def test_saves_both_date_and_time
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :utc do
+ with_timezone_config default: :utc do
time_values = [1807, 2, 10, 15, 30, 45]
# create DateTime value with local time zone offset
local_offset = Rational(Time.local(*time_values).utc_offset, 86400)
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index b277ef0317..9d7f57bf85 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -125,30 +125,30 @@ class DirtyTest < ActiveRecord::TestCase
end
def test_time_attributes_changes_without_time_zone
- target = Class.new(ActiveRecord::Base)
- target.table_name = 'pirates'
+ with_timezone_config aware_attributes: false do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'pirates'
- target.time_zone_aware_attributes = false
+ # New record - no changes.
+ pirate = target.new
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
- # New record - no changes.
- pirate = target.new
- assert !pirate.created_on_changed?
- assert_nil pirate.created_on_change
+ # Saved - no changes.
+ pirate.catchphrase = 'arrrr, time zone!!'
+ pirate.save!
+ assert !pirate.created_on_changed?
+ assert_nil pirate.created_on_change
- # Saved - no changes.
- pirate.catchphrase = 'arrrr, time zone!!'
- pirate.save!
- assert !pirate.created_on_changed?
- assert_nil pirate.created_on_change
-
- # Change created_on.
- old_created_on = pirate.created_on
- pirate.created_on = Time.now + 1.day
- assert pirate.created_on_changed?
- # kind_of does not work because
- # ActiveSupport::TimeWithZone.name == 'Time'
- assert_instance_of Time, pirate.created_on_was
- assert_equal old_created_on, pirate.created_on_was
+ # Change created_on.
+ old_created_on = pirate.created_on
+ pirate.created_on = Time.now + 1.day
+ assert pirate.created_on_changed?
+ # kind_of does not work because
+ # ActiveSupport::TimeWithZone.name == 'Time'
+ assert_instance_of Time, pirate.created_on_was
+ assert_equal old_created_on, pirate.created_on_was
+ end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 949e994f8d..4188b32731 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -11,6 +11,7 @@ require 'models/project'
require 'models/developer'
require 'models/customer'
require 'models/toy'
+require 'models/matey'
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations
@@ -51,6 +52,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.exists?(heading: "The First Topic")
assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true)
assert_equal true, Topic.exists?(["parent_id = ?", 1])
+ assert_equal true, Topic.exists?(id: [1, 9999])
assert_equal false, Topic.exists?(45)
assert_equal false, Topic.exists?(Topic.new)
@@ -477,7 +479,7 @@ class FinderTest < ActiveRecord::TestCase
def test_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :local do
+ with_timezone_config default: :local do
topic = Topic.first
assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getutc]).first
end
@@ -486,7 +488,7 @@ class FinderTest < ActiveRecord::TestCase
def test_hash_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :local do
+ with_timezone_config default: :local do
topic = Topic.first
assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getutc}).first
end
@@ -495,7 +497,7 @@ class FinderTest < ActiveRecord::TestCase
def test_condition_local_time_interpolation_with_default_timezone_utc
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :utc do
+ with_timezone_config default: :utc do
topic = Topic.first
assert_equal topic, Topic.all.merge!(:where => ['written_on = ?', topic.written_on.getlocal]).first
end
@@ -504,7 +506,7 @@ class FinderTest < ActiveRecord::TestCase
def test_hash_condition_local_time_interpolation_with_default_timezone_utc
with_env_tz 'America/New_York' do
- with_active_record_default_timezone :utc do
+ with_timezone_config default: :utc do
topic = Topic.first
assert_equal topic, Topic.all.merge!(:where => {:written_on => topic.written_on.getlocal}).first
end
@@ -859,6 +861,12 @@ class FinderTest < ActiveRecord::TestCase
Toy.reset_primary_key
end
+ def test_find_without_primary_key
+ assert_raises(ActiveRecord::UnknownPrimaryKey) do
+ Matey.find(1)
+ end
+ end
+
def test_finder_with_offset_string
assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.all.merge!(:offset => "3").to_a }
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 4e877aed2e..bffff07089 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -84,6 +84,12 @@ class FixturesTest < ActiveRecord::TestCase
assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}"
end
+ def test_create_symbol_fixtures_is_deprecated
+ assert_deprecated do
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => 'Course') { Course.connection }
+ end
+ end
+
def test_attributes
topics = create_fixtures("topics").first
assert_equal("The First Topic", topics["first"]["title"])
@@ -247,7 +253,8 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_fixtures_are_set_up_with_database_env_variable
- ENV.stubs(:[]).with("DATABASE_URL").returns("sqlite3:///:memory:")
+ db_url_tmp = ENV['DATABASE_URL']
+ ENV['DATABASE_URL'] = "sqlite3:///:memory:"
ActiveRecord::Base.stubs(:configurations).returns({})
test_case = Class.new(ActiveRecord::TestCase) do
fixtures :accounts
@@ -260,6 +267,43 @@ class FixturesTest < ActiveRecord::TestCase
result = test_case.new(:test_fixtures).run
assert result.passed?, "Expected #{result.name} to pass:\n#{result}"
+ ensure
+ ENV['DATABASE_URL'] = db_url_tmp
+ end
+end
+
+class HasManyThroughFixture < ActiveSupport::TestCase
+ def make_model(name)
+ Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
+ end
+
+ def test_has_many_through
+ pt = make_model "ParrotTreasure"
+ parrot = make_model "Parrot"
+ treasure = make_model "Treasure"
+
+ pt.table_name = "parrots_treasures"
+ pt.belongs_to :parrot, :class => parrot
+ pt.belongs_to :treasure, :class => treasure
+
+ parrot.has_many :parrot_treasures, :class => pt
+ parrot.has_many :treasures, :through => :parrot_treasures
+
+ parrots = File.join FIXTURES_ROOT, 'parrots'
+
+ fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ rows = fs.table_rows
+ assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrots_treasures']
+ end
+
+ def load_has_and_belongs_to_many
+ parrot = make_model "Parrot"
+ parrot.has_and_belongs_to_many :treasures
+
+ parrots = File.join FIXTURES_ROOT, 'parrots'
+
+ fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots
+ fs.table_rows
end
end
diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb
index 490b599fb6..981a75faf6 100644
--- a/activerecord/test/cases/forbidden_attributes_protection_test.rb
+++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb
@@ -61,4 +61,9 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase
assert_equal 'Guille', person.first_name
assert_equal 'm', person.gender
end
+
+ def test_blank_attributes_should_not_raise
+ person = Person.new
+ assert_nil person.assign_attributes(ProtectedParams.new({}))
+ end
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index f96978aff8..2af647e2d7 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -49,11 +49,58 @@ ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
-def with_active_record_default_timezone(zone)
- old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone
+def with_timezone_config(cfg)
+ verify_default_timezone_config
+
+ old_default_zone = ActiveRecord::Base.default_timezone
+ old_awareness = ActiveRecord::Base.time_zone_aware_attributes
+ old_zone = Time.zone
+
+ if cfg.has_key?(:default)
+ ActiveRecord::Base.default_timezone = cfg[:default]
+ end
+ if cfg.has_key?(:aware_attributes)
+ ActiveRecord::Base.time_zone_aware_attributes = cfg[:aware_attributes]
+ end
+ if cfg.has_key?(:zone)
+ Time.zone = cfg[:zone]
+ end
yield
ensure
- ActiveRecord::Base.default_timezone = old_zone
+ ActiveRecord::Base.default_timezone = old_default_zone
+ ActiveRecord::Base.time_zone_aware_attributes = old_awareness
+ Time.zone = old_zone
+end
+
+# This method makes sure that tests don't leak global state related to time zones.
+EXPECTED_ZONE = nil
+EXPECTED_DEFAULT_TIMEZONE = :utc
+EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES = false
+def verify_default_timezone_config
+ if Time.zone != EXPECTED_ZONE
+ $stderr.puts <<-MSG
+\n#{self.to_s}
+ Global state `Time.zone` was leaked.
+ Expected: #{EXPECTED_ZONE}
+ Got: #{Time.zone}
+ MSG
+ end
+ if ActiveRecord::Base.default_timezone != EXPECTED_DEFAULT_TIMEZONE
+ $stderr.puts <<-MSG
+\n#{self.to_s}
+ Global state `ActiveRecord::Base.default_timezone` was leaked.
+ Expected: #{EXPECTED_DEFAULT_TIMEZONE}
+ Got: #{ActiveRecord::Base.default_timezone}
+ MSG
+ end
+ if ActiveRecord::Base.time_zone_aware_attributes != EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES
+ $stderr.puts <<-MSG
+\n#{self.to_s}
+ Global state `ActiveRecord::Base.time_zone_aware_attributes` was leaked.
+ Expected: #{EXPECTED_TIME_ZONE_AWARE_ATTRIBUTES}
+ Got: #{ActiveRecord::Base.time_zone_aware_attributes}
+ MSG
+ end
end
unless ENV['FIXTURE_DEBUG']
@@ -119,21 +166,24 @@ class << Time
end
end
-module LogIntercepter
- attr_accessor :logged, :intercepted
- def self.extended(base)
- base.logged = []
- end
- def log(sql, name = 'SQL', binds = [], &block)
- if @intercepted
- @logged << [sql, name, binds]
- yield
- else
- super(sql, name,binds, &block)
- end
+class SQLSubscriber
+ attr_reader :logged
+ attr_reader :payloads
+
+ def initialize
+ @logged = []
+ @payloads = []
end
+
+ def start(name, id, payload)
+ @payloads << payload
+ @logged << [payload[:sql], payload[:name], payload[:binds]]
+ end
+
+ def finish(name, id, payload); end
end
+
module InTimeZone
private
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index a9be132801..73cf99a5d7 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -313,8 +313,12 @@ class InheritanceTest < ActiveRecord::TestCase
assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132")
assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save }
end
-end
+ def test_scope_inherited_properly
+ assert_nothing_raised { Company.of_first_firm }
+ assert_nothing_raised { Client.of_first_firm }
+ end
+end
class InheritanceComputeTypeTest < ActiveRecord::TestCase
fixtures :companies
diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb
index f5daca2fa8..406aacb056 100644
--- a/activerecord/test/cases/integration_test.rb
+++ b/activerecord/test/cases/integration_test.rb
@@ -23,17 +23,12 @@ class IntegrationTest < ActiveRecord::TestCase
end
def test_cache_key_for_existing_record_is_not_timezone_dependent
- ActiveRecord::Base.time_zone_aware_attributes = true
-
- Time.zone = 'UTC'
utc_key = Developer.first.cache_key
- Time.zone = 'EST'
- est_key = Developer.first.cache_key
-
- assert_equal utc_key, est_key
- ensure
- Time.zone = 'UTC'
+ with_timezone_config zone: "EST" do
+ est_key = Developer.first.cache_key
+ assert_equal utc_key, est_key
+ end
end
def test_cache_key_format_for_existing_record_with_updated_at
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index dfa12cb97c..a16ed963fe 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -272,6 +272,10 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert p.treasures.empty?
assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
end
+
+ def test_quoted_locking_column_is_deprecated
+ assert_deprecated { ActiveRecord::Base.quoted_locking_column }
+ end
end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 931caff727..ed32d68530 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -311,14 +311,23 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_schema_migrations_table_name
+ original_schema_migrations_table_name = ActiveRecord::Migrator.schema_migrations_table_name
+
+ assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
Reminder.reset_table_name
assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ ActiveRecord::Base.schema_migrations_table_name = "changed"
+ Reminder.reset_table_name
+ assert_equal "prefix_changed_suffix", ActiveRecord::Migrator.schema_migrations_table_name
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
Reminder.reset_table_name
- assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
+ assert_equal "changed", ActiveRecord::Migrator.schema_migrations_table_name
+ ensure
+ ActiveRecord::Base.schema_migrations_table_name = original_schema_migrations_table_name
+ Reminder.reset_table_name
end
def test_proper_table_name_on_migrator
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 08b3408665..9124105e6d 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -1,6 +1,7 @@
require "cases/helper"
require 'models/company_in_module'
require 'models/shop'
+require 'models/developer'
class ModulesTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :projects, :developers, :collections, :products, :variants
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index ce21760645..b82409bfbe 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -5,16 +5,6 @@ require 'models/customer'
class MultiParameterAttributeTest < ActiveRecord::TestCase
fixtures :topics
- def setup
- ActiveRecord::Base.time_zone_aware_attributes = false
- ActiveRecord::Base.default_timezone = :local
- Time.zone = nil
- end
-
- def teardown
- ActiveRecord::Base.default_timezone = :utc
- end
-
def test_multiparameter_attributes_on_date
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
topic = Topic.find(1)
@@ -86,13 +76,15 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ with_timezone_config default: :local do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ end
end
def test_multiparameter_attributes_on_time_with_no_date
@@ -152,13 +144,15 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
- "written_on(5i)" => "12", "written_on(6i)" => "02"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
+ with_timezone_config default: :local do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
+ "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
+ end
end
def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
@@ -180,6 +174,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic.attributes = attributes
assert_nil topic.written_on
end
+
def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
attributes = {
"written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
@@ -191,56 +186,56 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time_with_utc
- ActiveRecord::Base.default_timezone = :utc
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ with_timezone_config default: :utc do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ end
end
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
- ActiveRecord::Base.time_zone_aware_attributes = true
- ActiveRecord::Base.default_timezone = :utc
- Time.zone = ActiveSupport::TimeZone[-28800]
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
- assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
- assert_equal Time.zone, topic.written_on.time_zone
+ with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
+ assert_equal Time.zone, topic.written_on.time_zone
+ end
end
def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
- Time.zone = ActiveSupport::TimeZone[-28800]
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
+ with_timezone_config default: :local, aware_attributes: false, zone: -28800 do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
+ end
end
def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
- ActiveRecord::Base.time_zone_aware_attributes = true
- ActiveRecord::Base.default_timezone = :utc
- Time.zone = ActiveSupport::TimeZone[-28800]
- Topic.skip_time_zone_conversion_for_attributes = [:written_on]
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
+ with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
+ Topic.skip_time_zone_conversion_for_attributes = [:written_on]
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
+ end
ensure
Topic.skip_time_zone_conversion_for_attributes = []
end
@@ -248,30 +243,31 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
# Oracle, and Sybase do not have a TIME datatype.
unless current_adapter?(:OracleAdapter, :SybaseAdapter)
def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
- ActiveRecord::Base.time_zone_aware_attributes = true
- ActiveRecord::Base.default_timezone = :utc
- Time.zone = ActiveSupport::TimeZone[-28800]
+ with_timezone_config default: :utc, aware_attributes: true, zone: -28800 do
+ attributes = {
+ "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
+ "bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
+ assert topic.bonus_time.utc?
+ end
+ end
+ end
+
+ def test_multiparameter_attributes_on_time_with_empty_seconds
+ with_timezone_config default: :local do
attributes = {
- "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
- "bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
- assert topic.bonus_time.utc?
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
end
- def test_multiparameter_attributes_on_time_with_empty_seconds
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
- end
-
def test_multiparameter_attributes_setting_time_attribute
return skip "Oracle does not have TIME data type" if current_adapter? :OracleAdapter
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 30dc2a34c6..6cd3e2154e 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -419,10 +419,6 @@ class PersistenceTest < ActiveRecord::TestCase
assert !Topic.find(1).approved?
end
- def test_update_attribute_does_not_choke_on_nil
- assert Topic.find(1).update(nil)
- end
-
def test_update_attribute_for_readonly_attribute
minivan = Minivan.find('m1')
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
@@ -701,6 +697,17 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal topic.title, Topic.find(1234).title
end
+ def test_update_attributes_parameters
+ topic = Topic.find(1)
+ assert_nothing_raised do
+ topic.update_attributes({})
+ end
+
+ assert_raises(ArgumentError) do
+ topic.update_attributes(nil)
+ end
+ end
+
def test_update!
Reply.validates_presence_of(:title)
reply = Reply.find(2)
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 44b2064110..e2439b9a24 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -53,28 +53,28 @@ module ActiveRecord
end
def test_quoted_time_utc
- with_active_record_default_timezone :utc do
+ with_timezone_config default: :utc do
t = Time.now
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_local
- with_active_record_default_timezone :local do
+ with_timezone_config default: :local do
t = Time.now
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_crazy
- with_active_record_default_timezone :asdfasdf do
+ with_timezone_config default: :asdfasdf do
t = Time.now
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_datetime_utc
- with_active_record_default_timezone :utc do
+ with_timezone_config default: :utc do
t = DateTime.now
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
@@ -83,7 +83,7 @@ module ActiveRecord
###
# DateTime doesn't define getlocal, so make sure it does nothing
def test_quoted_datetime_local
- with_active_record_default_timezone :local do
+ with_timezone_config default: :local do
t = DateTime.now
assert_equal t.to_s(:db), @quoter.quoted_date(t)
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 0e5c7df2cc..d7ad5ed29f 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -18,6 +18,11 @@ require 'models/subscription'
require 'models/tag'
require 'models/sponsor'
require 'models/edge'
+require 'models/hotel'
+require 'models/chef'
+require 'models/department'
+require 'models/cake_designer'
+require 'models/drink_designer'
class ReflectionTest < ActiveRecord::TestCase
include ActiveRecord::Reflection
@@ -227,6 +232,17 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal expected, actual
end
+ def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case
+ @hotel = Hotel.create!
+ @department = @hotel.departments.create!
+ @department.chefs.create!(employable: CakeDesigner.create!)
+ @department.chefs.create!(employable: DrinkDesigner.create!)
+
+ assert_equal 1, @hotel.cake_designers.size
+ assert_equal 1, @hotel.drink_designers.size
+ assert_equal 2, @hotel.chefs.size
+ end
+
def test_nested?
assert !Author.reflect_on_association(:comments).nested?
assert Author.reflect_on_association(:tags).nested?
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
new file mode 100644
index 0000000000..c171c5e14e
--- /dev/null
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -0,0 +1,98 @@
+require 'cases/helper'
+require 'models/post'
+require 'models/comment'
+
+module ActiveRecord
+ class DelegationTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def assert_responds(target, method)
+ assert target.respond_to?(method)
+ assert_nothing_raised do
+ method_arity = target.to_a.method(method).arity
+
+ if method_arity.zero?
+ target.send(method)
+ elsif method_arity < 0
+ if method == :shuffle!
+ target.send(method)
+ else
+ target.send(method, 1)
+ end
+ else
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+
+ class DelegationAssociationTest < DelegationTest
+ def target
+ Post.first.comments
+ end
+
+ [:map, :collect].each do |method|
+ test "##{method} is delgated" do
+ assert_responds(target, method)
+ assert_equal(target.pluck(:body), target.send(method) {|post| post.body })
+ end
+
+ test "##{method}! is not delgated" do
+ assert_deprecated do
+ assert_responds(target, "#{method}!")
+ end
+ end
+ end
+
+ [:compact!, :flatten!, :reject!, :reverse!, :rotate!,
+ :shuffle!, :slice!, :sort!, :sort_by!].each do |method|
+ test "##{method} delegation is deprecated" do
+ assert_deprecated do
+ assert_responds(target, method)
+ end
+ end
+ end
+
+ [:select!, :uniq!].each do |method|
+ test "##{method} is implemented" do
+ assert_responds(target, method)
+ end
+ end
+ end
+
+ class DelegationRelationTest < DelegationTest
+ def target
+ Comment.where.not(body: nil)
+ end
+
+ [:map, :collect].each do |method|
+ test "##{method} is delgated" do
+ assert_responds(target, method)
+ assert_equal(target.pluck(:body), target.send(method) {|post| post.body })
+ end
+
+ test "##{method}! is not delgated" do
+ assert_deprecated do
+ assert_responds(target, "#{method}!")
+ end
+ end
+ end
+
+ [:compact!, :flatten!, :reject!, :reverse!, :rotate!,
+ :shuffle!, :slice!, :sort!, :sort_by!].each do |method|
+ test "##{method} delegation is deprecated" do
+ assert_deprecated do
+ assert_responds(target, method)
+ end
+ end
+ end
+
+ [:select!, :uniq!].each do |method|
+ test "##{method} is triggers an immutable error" do
+ assert_raises ActiveRecord::ImmutableRelation do
+ assert_responds(target, method)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
new file mode 100644
index 0000000000..020fb24afa
--- /dev/null
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -0,0 +1,148 @@
+require 'cases/helper'
+require 'models/post'
+
+module ActiveRecord
+ class RelationMutationTest < ActiveSupport::TestCase
+ class FakeKlass < Struct.new(:table_name, :name)
+ extend ActiveRecord::Delegation::DelegateCache
+ inherited self
+
+ def arel_table
+ Post.arel_table
+ end
+
+ def connection
+ Post.connection
+ end
+
+ def relation_delegate_class(klass)
+ self.class.relation_delegate_class(klass)
+ end
+ end
+
+ def relation
+ @relation ||= Relation.new FakeKlass.new('posts'), :b
+ end
+
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal [:foo], relation.public_send("#{method}_values")
+ end
+ end
+
+ test '#order!' do
+ assert relation.order!('name ASC').equal?(relation)
+ assert_equal ['name ASC'], relation.order_values
+ end
+
+ test '#order! with symbol prepends the table name' do
+ assert relation.order!(:name).equal?(relation)
+ node = relation.order_values.first
+ assert node.ascending?
+ assert_equal :name, node.expr.name
+ assert_equal "posts", node.expr.relation.name
+ end
+
+ test '#order! on non-string does not attempt regexp match for references' do
+ obj = Object.new
+ obj.expects(:=~).never
+ assert relation.order!(obj)
+ assert_equal [obj], relation.order_values
+ end
+
+ test '#references!' do
+ assert relation.references!(:foo).equal?(relation)
+ assert relation.references_values.include?('foo')
+ end
+
+ test 'extending!' do
+ mod, mod2 = Module.new, Module.new
+
+ assert relation.extending!(mod).equal?(relation)
+ assert_equal [mod], relation.extending_values
+ assert relation.is_a?(mod)
+
+ relation.extending!(mod2)
+ assert_equal [mod, mod2], relation.extending_values
+ end
+
+ test 'extending! with empty args' do
+ relation.extending!
+ assert_equal [], relation.extending_values
+ end
+
+ (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal :foo, relation.public_send("#{method}_value")
+ end
+ end
+
+ test '#from!' do
+ assert relation.from!('foo').equal?(relation)
+ assert_equal ['foo', nil], relation.from_value
+ end
+
+ test '#lock!' do
+ assert relation.lock!('foo').equal?(relation)
+ assert_equal 'foo', relation.lock_value
+ end
+
+ test '#reorder!' do
+ relation = self.relation.order('foo')
+
+ assert relation.reorder!('bar').equal?(relation)
+ assert_equal ['bar'], relation.order_values
+ assert relation.reordering_value
+ end
+
+ test '#reorder! with symbol prepends the table name' do
+ assert relation.reorder!(:name).equal?(relation)
+ node = relation.order_values.first
+
+ assert node.ascending?
+ assert_equal :name, node.expr.name
+ assert_equal "posts", node.expr.relation.name
+ end
+
+ test 'reverse_order!' do
+ assert relation.reverse_order!.equal?(relation)
+ assert relation.reverse_order_value
+ relation.reverse_order!
+ assert !relation.reverse_order_value
+ end
+
+ test 'create_with!' do
+ assert relation.create_with!(foo: 'bar').equal?(relation)
+ assert_equal({foo: 'bar'}, relation.create_with_value)
+ end
+
+ test 'test_merge!' do
+ assert relation.merge!(where: :foo).equal?(relation)
+ assert_equal [:foo], relation.where_values
+ end
+
+ test 'merge with a proc' do
+ assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values
+ end
+
+ test 'none!' do
+ assert relation.none!.equal?(relation)
+ assert_equal [NullRelation], relation.extending_values
+ assert relation.is_a?(NullRelation)
+ end
+
+ test 'distinct!' do
+ relation.distinct! :foo
+ assert_equal :foo, relation.distinct_value
+ assert_equal :foo, relation.uniq_value # deprecated access
+ end
+
+ test 'uniq! was replaced by distinct!' do
+ relation.uniq! :foo
+ assert_equal :foo, relation.distinct_value
+ assert_equal :foo, relation.uniq_value # deprecated access
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index a695087426..70d113fb39 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -193,6 +193,14 @@ module ActiveRecord
assert_equal ['foo = bar'], relation.where_values
end
+ def test_merging_readonly_false
+ relation = Relation.new FakeKlass, :b
+ readonly_false_relation = relation.readonly(false)
+ # test merging in both directions
+ assert_equal false, relation.merge(readonly_false_relation).readonly_value
+ assert_equal false, readonly_false_relation.merge(relation).readonly_value
+ end
+
def test_relation_merging_with_merged_joins_as_symbols
special_comments_with_ratings = SpecialComment.joins(:ratings)
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
@@ -215,139 +223,4 @@ module ActiveRecord
end
end
-
- class RelationMutationTest < ActiveSupport::TestCase
- class FakeKlass < Struct.new(:table_name, :name)
- extend ActiveRecord::Delegation::DelegateCache
- inherited self
-
- def arel_table
- Post.arel_table
- end
-
- def connection
- Post.connection
- end
-
- def relation_delegate_class(klass)
- self.class.relation_delegate_class(klass)
- end
- end
-
- def relation
- @relation ||= Relation.new FakeKlass.new('posts'), :b
- end
-
- (Relation::MULTI_VALUE_METHODS - [:references, :extending, :order]).each do |method|
- test "##{method}!" do
- assert relation.public_send("#{method}!", :foo).equal?(relation)
- assert_equal [:foo], relation.public_send("#{method}_values")
- end
- end
-
- test "#order!" do
- assert relation.order!('name ASC').equal?(relation)
- assert_equal ['name ASC'], relation.order_values
- end
-
- test "#order! with symbol prepends the table name" do
- assert relation.order!(:name).equal?(relation)
- node = relation.order_values.first
- assert node.ascending?
- assert_equal :name, node.expr.name
- assert_equal "posts", node.expr.relation.name
- end
-
- test "#order! on non-string does not attempt regexp match for references" do
- obj = Object.new
- obj.expects(:=~).never
- assert relation.order!(obj)
- assert_equal [obj], relation.order_values
- end
-
- test '#references!' do
- assert relation.references!(:foo).equal?(relation)
- assert relation.references_values.include?('foo')
- end
-
- test 'extending!' do
- mod, mod2 = Module.new, Module.new
-
- assert relation.extending!(mod).equal?(relation)
- assert_equal [mod], relation.extending_values
- assert relation.is_a?(mod)
-
- relation.extending!(mod2)
- assert_equal [mod, mod2], relation.extending_values
- end
-
- test 'extending! with empty args' do
- relation.extending!
- assert_equal [], relation.extending_values
- end
-
- (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
- test "##{method}!" do
- assert relation.public_send("#{method}!", :foo).equal?(relation)
- assert_equal :foo, relation.public_send("#{method}_value")
- end
- end
-
- test '#from!' do
- assert relation.from!('foo').equal?(relation)
- assert_equal ['foo', nil], relation.from_value
- end
-
- test '#lock!' do
- assert relation.lock!('foo').equal?(relation)
- assert_equal 'foo', relation.lock_value
- end
-
- test '#reorder!' do
- relation = self.relation.order('foo')
-
- assert relation.reorder!('bar').equal?(relation)
- assert_equal ['bar'], relation.order_values
- assert relation.reordering_value
- end
-
- test 'reverse_order!' do
- assert relation.reverse_order!.equal?(relation)
- assert relation.reverse_order_value
- relation.reverse_order!
- assert !relation.reverse_order_value
- end
-
- test 'create_with!' do
- assert relation.create_with!(foo: 'bar').equal?(relation)
- assert_equal({foo: 'bar'}, relation.create_with_value)
- end
-
- def test_merge!
- assert relation.merge!(where: :foo).equal?(relation)
- assert_equal [:foo], relation.where_values
- end
-
- test 'merge with a proc' do
- assert_equal [:foo], relation.merge(-> { where(:foo) }).where_values
- end
-
- test 'none!' do
- assert relation.none!.equal?(relation)
- assert_equal [NullRelation], relation.extending_values
- assert relation.is_a?(NullRelation)
- end
-
- test "distinct!" do
- relation.distinct! :foo
- assert_equal :foo, relation.distinct_value
- assert_equal :foo, relation.uniq_value # deprecated access
- end
-
- test "uniq! was replaced by distinct!" do
- relation.uniq! :foo
- assert_equal :foo, relation.distinct_value
- assert_equal :foo, relation.uniq_value # deprecated access
- end
- end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index e1a760d240..c9c7ac04b3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -42,6 +42,11 @@ class RelationTest < ActiveRecord::TestCase
end
def test_two_scopes_with_includes_should_not_drop_any_include
+ # heat habtm cache
+ car = Car.incl_engines.incl_tyres.first
+ car.tyres.length
+ car.engines.length
+
car = Car.incl_engines.incl_tyres.first
assert_no_queries { car.tyres.length }
assert_no_queries { car.engines.length }
@@ -139,6 +144,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a
end
+ def test_finding_with_subquery_with_binds
+ relation = Post.first.comments
+ assert_equal relation.to_a, Comment.select('*').from(relation).to_a
+ assert_equal relation.to_a, Comment.select('subquery.*').from(relation).to_a
+ assert_equal relation.to_a, Comment.select('a.*').from(relation, :a).to_a
+ end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name)
@@ -262,7 +274,7 @@ class RelationTest < ActiveRecord::TestCase
def test_none_chained_to_methods_firing_queries_straight_to_db
assert_no_queries do
- assert_equal [], Developer.none.pluck(:id) # => uses select_all
+ assert_equal [], Developer.none.pluck(:id, :name)
assert_equal 0, Developer.none.delete_all
assert_equal 0, Developer.none.update_all(:name => 'David')
assert_equal 0, Developer.none.delete(1)
@@ -283,7 +295,7 @@ class RelationTest < ActiveRecord::TestCase
def test_null_relation_calculations_methods
assert_no_queries do
assert_equal 0, Developer.none.count
- assert_equal 0, Developer.none.calculate(:count, nil, {})
+ assert_equal 0, Developer.none.calculate(:count, nil)
assert_equal nil, Developer.none.calculate(:average, 'salary')
end
end
@@ -293,6 +305,10 @@ class RelationTest < ActiveRecord::TestCase
assert_equal({}, Developer.none.where_values_hash)
end
+ def test_null_relation_where_values_hash
+ assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -470,6 +486,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Developer.where(name: 'David').map(&:id).sort, developers
end
+ def test_includes_with_select
+ query = Post.select('comments_count AS ranking').order('ranking').includes(:comments)
+ .where(comments: { id: 1 })
+
+ assert_equal ['comments_count AS ranking'], query.select_values
+ assert_equal 1, query.to_a.size
+ end
+
def test_loading_with_one_association
posts = Post.preload(:comments)
post = posts.find { |p| p.id == 1 }
@@ -610,6 +634,36 @@ class RelationTest < ActiveRecord::TestCase
relation = Author.where(:id => Author.where(:id => david.id))
assert_equal [david], relation.to_a
}
+
+ assert_queries(1) {
+ relation = Author.where('id in (?)', Author.where(id: david).select(:id))
+ assert_equal [david], relation.to_a
+ }
+
+ assert_queries(1) do
+ relation = Author.where('id in (:author_ids)', author_ids: Author.where(id: david).select(:id))
+ assert_equal [david], relation.to_a
+ end
+ end
+
+ def test_find_all_using_where_with_relation_with_bound_values
+ david = authors(:david)
+ davids_posts = david.posts.order(:id).to_a
+
+ assert_queries(1) do
+ relation = Post.where(id: david.posts.select(:id))
+ assert_equal davids_posts, relation.order(:id).to_a
+ end
+
+ assert_queries(1) do
+ relation = Post.where('id in (?)', david.posts.select(:id))
+ assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as bind variables'
+ end
+
+ assert_queries(1) do
+ relation = Post.where('id in (:post_ids)', post_ids: david.posts.select(:id))
+ assert_equal davids_posts, relation.order(:id).to_a, 'should process Relation as named bind variables'
+ end
end
def test_find_all_using_where_with_relation_and_alternate_primary_key
@@ -1344,6 +1398,24 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [], scope.references_values
end
+ def test_automatically_added_reorder_references
+ scope = Post.reorder('comments.body')
+ assert_equal %w(comments), scope.references_values
+
+ scope = Post.reorder('comments.body', 'yaks.body')
+ assert_equal %w(comments yaks), scope.references_values
+
+ # Don't infer yaks, let's not go down that road again...
+ scope = Post.reorder('comments.body, yaks.body')
+ assert_equal %w(comments), scope.references_values
+
+ scope = Post.reorder('comments.body asc')
+ assert_equal %w(comments), scope.references_values
+
+ scope = Post.reorder('foo(comments.body)')
+ assert_equal [], scope.references_values
+ end
+
def test_presence
topics = Topic.all
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 082570c55b..766b2ff2ef 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -1,5 +1,7 @@
require "cases/helper"
require 'models/binary'
+require 'models/author'
+require 'models/post'
class SanitizeTest < ActiveRecord::TestCase
def setup
@@ -9,7 +11,7 @@ class SanitizeTest < ActiveRecord::TestCase
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
quoted_column_name = ActiveRecord::Base.connection.quote_column_name("name")
quoted_table_name = ActiveRecord::Base.connection.quote_table_name("adorable_animals")
- expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}"
+ expected_value = "#{quoted_table_name}.#{quoted_column_name} = #{quoted_bambi}"
assert_equal expected_value, Binary.send(:sanitize_sql_hash, {adorable_animals: {name: 'Bambi'}})
end
@@ -31,4 +33,17 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"])
assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars])
end
+
+ def test_sanitize_sql_array_handles_relations
+ david = Author.create!(name: 'David')
+ david_posts = david.posts.select(:id)
+
+ sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i
+
+ select_author_sql = Post.send(:sanitize_sql_array, ['id in (?)', david_posts])
+ assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for bind variables')
+
+ select_author_sql = Post.send(:sanitize_sql_array, ['id in (:post_ids)', post_ids: david_posts])
+ assert_match(sub_query_pattern, select_author_sql, 'should sanitize `Relation` as subquery for named bind variables')
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index a48ae1036f..1ee8e60924 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -202,6 +202,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved"
end
+ def test_schema_dump_should_use_false_as_default
+ output = standard_dump
+ assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output
+ end
+
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_schema_dump_should_not_add_default_value_for_mysql_text_field
output = standard_dump
@@ -299,7 +304,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_uuid_shorthand_definition
output = standard_dump
- if %r{create_table "poistgresql_uuids"} =~ output
+ if %r{create_table "postgresql_uuids"} =~ output
assert_match %r{t.uuid "guid"}, output
end
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index cd7d91ff85..76f395ba83 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -122,17 +122,25 @@ class DefaultScopingTest < ActiveRecord::TestCase
end
def test_unscope_with_where_attributes
- expected = Developer.order('salary DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect { |dev| dev.name }
+ expected = Developer.order('salary DESC').collect(&:name)
+ received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect(&:name)
assert_equal expected, received
- expected_2 = Developer.order('salary DESC').collect { |dev| dev.name }
- received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect { |dev| dev.name }
+ expected_2 = Developer.order('salary DESC').collect(&:name)
+ received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect(&:name)
assert_equal expected_2, received_2
- expected_3 = Developer.order('salary DESC').collect { |dev| dev.name }
- received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect { |dev| dev.name }
+ expected_3 = Developer.order('salary DESC').collect(&:name)
+ received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name)
assert_equal expected_3, received_3
+
+ expected_4 = Developer.order('salary DESC').collect(&:name)
+ received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name)
+ assert_equal expected_4, received_4
+
+ expected_5 = Developer.order('salary DESC').collect(&:name)
+ received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name)
+ assert_equal expected_5, received_5
end
def test_unscope_multiple_where_clauses
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index b49c27bc78..bc67da8d27 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -211,16 +211,15 @@ class SerializedAttributeTest < ActiveRecord::TestCase
end
def test_serialize_attribute_via_select_method_when_time_zone_available
- ActiveRecord::Base.time_zone_aware_attributes = true
- Topic.serialize(:content, MyObject)
+ with_timezone_config aware_attributes: true do
+ Topic.serialize(:content, MyObject)
- myobj = MyObject.new('value1', 'value2')
- topic = Topic.create(content: myobj)
+ myobj = MyObject.new('value1', 'value2')
+ topic = Topic.create(content: myobj)
- assert_equal(myobj, Topic.select(:content).find(topic.id).content)
- assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content }
- ensure
- ActiveRecord::Base.time_zone_aware_attributes = false
+ assert_equal(myobj, Topic.select(:content).find(topic.id).content)
+ assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content }
+ end
end
def test_serialize_attribute_can_be_serialized_in_an_integer_column
@@ -243,10 +242,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
myobj = MyObject.new('value1', 'value2')
Topic.create(content: myobj)
Topic.create(content: myobj)
-
- Topic.all.each do |topic|
- type = topic.instance_variable_get("@columns_hash")["content"]
- assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type)
- end
+ type = Topic.column_types["content"]
+ assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type)
end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index c2c56abacd..0c9f7ccd55 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -150,4 +150,16 @@ class StoreTest < ActiveRecord::TestCase
test "all stored attributes are returned" do
assert_equal [:color, :homepage, :favorite_food], Admin::User.stored_attributes[:settings]
end
+
+ test "stored_attributes are tracked per class" do
+ first_model = Class.new(ActiveRecord::Base) do
+ store_accessor :data, :color
+ end
+ second_model = Class.new(ActiveRecord::Base) do
+ store_accessor :data, :width, :height
+ end
+
+ assert_equal [:color], first_model.stored_attributes[:data]
+ assert_equal [:width, :height], second_model.stored_attributes[:data]
+ end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 816bd62751..bdcf31043a 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -280,6 +280,15 @@ module ActiveRecord
assert_match(/Could not dump the database structure/, warnings)
end
+
+ def test_structure_dump_with_port_number
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("mysqldump", "--port", "10000", "--result-file", filename, "--no-data", "test-db").returns(true)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(
+ @configuration.merge('port' => 10000),
+ filename)
+ end
end
class MySQLStructureLoadTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index f31896bc7f..90dac6399d 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -231,6 +231,13 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
+
+ def test_structure_load_accepts_path_with_spaces
+ filename = "awesome file.sql"
+ Kernel.expects(:system).with("psql -q -f awesome\\ file.sql my-app-db")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index a5cb22aaf6..980981903a 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -375,6 +375,36 @@ class TransactionTest < ActiveRecord::TestCase
assert_equal "Three", @three
end if Topic.connection.supports_savepoints?
+ def test_using_named_savepoints
+ Topic.transaction do
+ @first.approved = true
+ @first.save!
+ Topic.connection.create_savepoint("first")
+
+ @first.approved = false
+ @first.save!
+ Topic.connection.rollback_to_savepoint("first")
+ assert @first.reload.approved?
+
+ @first.approved = false
+ @first.save!
+ Topic.connection.release_savepoint("first")
+ assert_not @first.reload.approved?
+ end
+ end if Topic.connection.supports_savepoints?
+
+ def test_releasing_named_savepoints
+ Topic.transaction do
+ Topic.connection.create_savepoint("another")
+ Topic.connection.release_savepoint("another")
+
+ # The savepoint is now gone and we can't remove it again.
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Topic.connection.release_savepoint("another")
+ end
+ end
+ end
+
def test_rollback_when_commit_raises
Topic.connection.expects(:begin_db_transaction)
Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
@@ -391,7 +421,9 @@ class TransactionTest < ActiveRecord::TestCase
topic = Topic.new(:title => 'test')
topic.freeze
e = assert_raise(RuntimeError) { topic.save }
- assert_equal "can't modify frozen Hash", e.message
+ assert_match(/frozen/i, e.message) # Not good enough, but we can't do much
+ # about it since there is no specific error
+ # for frozen objects.
assert !topic.persisted?, 'not persisted'
assert_nil topic.id
assert topic.frozen?, 'not frozen'
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 68fa15de50..78fa2f935a 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -161,21 +161,17 @@ end
class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase
def test_should_serialize_datetime_with_timezone
- timezone, Time.zone = Time.zone, "Pacific Time (US & Canada)"
-
- toy = Toy.create(:name => 'Mickey', :updated_at => Time.utc(2006, 8, 1))
- assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
- ensure
- Time.zone = timezone
+ with_timezone_config zone: "Pacific Time (US & Canada)" do
+ toy = Toy.create(:name => 'Mickey', :updated_at => Time.utc(2006, 8, 1))
+ assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
+ end
end
def test_should_serialize_datetime_with_timezone_reloaded
- timezone, Time.zone = Time.zone, "Pacific Time (US & Canada)"
-
- toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload
- assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
- ensure
- Time.zone = timezone
+ with_timezone_config zone: "Pacific Time (US & Canada)" do
+ toy = Toy.create(:name => 'Minnie', :updated_at => Time.utc(2006, 8, 1)).reload
+ assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
+ end
end
end
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 302913e095..83a710b1b7 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -5,16 +5,10 @@ class YamlSerializationTest < ActiveRecord::TestCase
fixtures :topics
def test_to_yaml_with_time_with_zone_should_not_raise_exception
- tz = Time.zone
- Time.zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
- ActiveRecord::Base.time_zone_aware_attributes = true
-
- topic = Topic.new(:written_on => DateTime.now)
- assert_nothing_raised { topic.to_yaml }
-
- ensure
- Time.zone = tz
- ActiveRecord::Base.time_zone_aware_attributes = false
+ with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do
+ topic = Topic.new(:written_on => DateTime.now)
+ assert_nothing_raised { topic.to_yaml }
+ end
end
def test_roundtrip
diff --git a/activerecord/test/fixtures/sponsors.yml b/activerecord/test/fixtures/sponsors.yml
index bfc6b238b1..2da541c539 100644
--- a/activerecord/test/fixtures/sponsors.yml
+++ b/activerecord/test/fixtures/sponsors.yml
@@ -8,5 +8,5 @@ boring_club_sponsor_for_groucho:
sponsorable_type: Member
crazy_club_sponsor_for_groucho:
sponsor_club: crazy_club
- sponsorable_id: 2
+ sponsorable_id: 3
sponsorable_type: Member
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index feb828de31..794d1af43d 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -8,9 +8,7 @@ class Author < ActiveRecord::Base
has_many :posts_sorted_by_id_limited, -> { order('posts.id').limit(1) }, :class_name => "Post"
has_many :posts_with_categories, -> { includes(:categories) }, :class_name => "Post"
has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, :class_name => "Post"
- has_many :posts_containing_the_letter_a, :class_name => "Post"
has_many :posts_with_special_categorizations, :class_name => 'PostWithSpecialCategorization'
- has_many :posts_with_extension, :class_name => "Post"
has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, :class_name => 'Post'
has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, :class_name => 'Post'
has_many :comments, through: :posts do
@@ -31,8 +29,14 @@ class Author < ActiveRecord::Base
has_many :thinking_posts, -> { where(:title => 'So I was thinking') }, :dependent => :delete_all, :class_name => 'Post'
has_many :welcome_posts, -> { where(:title => 'Welcome to the weblog') }, :class_name => 'Post'
+ has_many :welcome_posts_with_comment,
+ -> { where(title: 'Welcome to the weblog').where('comments_count = ?', 1) },
+ class_name: 'Post'
+ has_many :welcome_posts_with_comments,
+ -> { where(title: 'Welcome to the weblog').where(Post.arel_table[:comments_count].gt(0)) },
+ class_name: 'Post'
+
has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments
- has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments
has_many :funky_comments, :through => :posts, :source => :comments
has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments
has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments
diff --git a/activerecord/test/models/auto_id.rb b/activerecord/test/models/auto_id.rb
index d720e2be5e..82c6544bd5 100644
--- a/activerecord/test/models/auto_id.rb
+++ b/activerecord/test/models/auto_id.rb
@@ -1,4 +1,4 @@
class AutoId < ActiveRecord::Base
- def self.table_name () "auto_id_tests" end
- def self.primary_key () "auto_id" end
+ self.table_name = "auto_id_tests"
+ self.primary_key = "auto_id"
end
diff --git a/activerecord/test/models/cake_designer.rb b/activerecord/test/models/cake_designer.rb
new file mode 100644
index 0000000000..9c57ef573a
--- /dev/null
+++ b/activerecord/test/models/cake_designer.rb
@@ -0,0 +1,3 @@
+class CakeDesigner < ActiveRecord::Base
+ has_one :chef, as: :employable
+end
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index a14a9febba..6d257dbe7e 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -1,12 +1,9 @@
class Car < ActiveRecord::Base
-
has_many :bulbs
has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy
has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb"
- has_many :frickinawesome_bulbs, -> { where :frickinawesome => true }, :class_name => "Bulb"
has_one :bulb
- has_one :frickinawesome_bulb, -> { where :frickinawesome => true }, :class_name => "Bulb"
has_many :tyres
has_many :engines, :dependent => :destroy
diff --git a/activerecord/test/models/chef.rb b/activerecord/test/models/chef.rb
new file mode 100644
index 0000000000..67a4e54f06
--- /dev/null
+++ b/activerecord/test/models/chef.rb
@@ -0,0 +1,3 @@
+class Chef < ActiveRecord::Base
+ belongs_to :employable, polymorphic: true
+end
diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb
index 545aa8110d..3d87eb795c 100644
--- a/activerecord/test/models/citation.rb
+++ b/activerecord/test/models/citation.rb
@@ -1,6 +1,3 @@
class Citation < ActiveRecord::Base
belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id
-
- belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id
- belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id
end
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index 816c5e6937..566e0873f1 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -2,7 +2,6 @@ class Club < ActiveRecord::Base
has_one :membership
has_many :memberships, :inverse_of => false
has_many :members, :through => :memberships
- has_many :current_memberships
has_one :sponsor
has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member"
belongs_to :category
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index c5d4ec0833..0b0b304121 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -11,6 +11,11 @@ class Company < AbstractCompany
has_many :contracts
has_many :developers, :through => :contracts
+ scope :of_first_firm, lambda {
+ joins(:account => :firm).
+ where('firms.id' => 1)
+ }
+
def arbitrary_method
"I am Jack's profound disappointment"
end
@@ -39,7 +44,7 @@ class Firm < Company
has_many :unsorted_clients, :class_name => "Client"
has_many :unsorted_clients_with_symbol, :class_name => :Client
has_many :clients_sorted_desc, -> { order "id DESC" }, :class_name => "Client"
- has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client"
+ has_many :clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :inverse_of => :firm
has_many :clients_ordered_by_name, -> { order "name" }, :class_name => "Client"
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
has_many :dependent_clients_of_firm, -> { order "id" }, :foreign_key => "client_of", :class_name => "Client", :dependent => :destroy
@@ -49,7 +54,6 @@ class Firm < Company
has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, :class_name => "Client"
has_many :clients_like_ms_with_hash_conditions, -> { where(:name => 'Microsoft').order("id") }, :class_name => "Client"
has_many :plain_clients, :class_name => 'Client'
- has_many :readonly_clients, -> { readonly }, :class_name => 'Client'
has_many :clients_using_primary_key, :class_name => 'Client',
:primary_key => 'name', :foreign_key => 'firm_name'
has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client',
@@ -118,6 +122,10 @@ class Client < Company
has_many :accounts, :through => :firm, :source => :accounts
belongs_to :account
+ validate do
+ firm
+ end
+
class RaisedOnSave < RuntimeError; end
attr_accessor :raise_on_save
before_save do
@@ -167,7 +175,6 @@ class ExclusivelyDependentFirm < Company
has_one :account, :foreign_key => "firm_id", :dependent => :delete
has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all
has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all
- has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(:name => 'BigShot Inc.') }, :foreign_key => "client_of", :class_name => "Client", :dependent => :delete_all
end
class SpecialClient < Client
diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb
index 2cf5aa7a85..cdf7b267b5 100644
--- a/activerecord/test/models/contract.rb
+++ b/activerecord/test/models/contract.rb
@@ -1,6 +1,7 @@
class Contract < ActiveRecord::Base
belongs_to :company
belongs_to :developer
+ belongs_to :firm, :foreign_key => 'company_id'
before_save :hi
after_save :bye
diff --git a/activerecord/test/models/department.rb b/activerecord/test/models/department.rb
new file mode 100644
index 0000000000..08004a0ed3
--- /dev/null
+++ b/activerecord/test/models/department.rb
@@ -0,0 +1,4 @@
+class Department < ActiveRecord::Base
+ has_many :chefs
+ belongs_to :hotel
+end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index c8e2be580e..a26de55758 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -38,6 +38,8 @@ class Developer < ActiveRecord::Base
has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id'
has_many :audit_logs
+ has_many :contracts
+ has_many :firms, :through => :contracts, :source => :firm
scope :jamises, -> { where(:name => 'Jamis') }
diff --git a/activerecord/test/models/drink_designer.rb b/activerecord/test/models/drink_designer.rb
new file mode 100644
index 0000000000..2db968ef11
--- /dev/null
+++ b/activerecord/test/models/drink_designer.rb
@@ -0,0 +1,3 @@
+class DrinkDesigner < ActiveRecord::Base
+ has_one :chef, as: :employable
+end
diff --git a/activerecord/test/models/hotel.rb b/activerecord/test/models/hotel.rb
new file mode 100644
index 0000000000..b352cd22f3
--- /dev/null
+++ b/activerecord/test/models/hotel.rb
@@ -0,0 +1,6 @@
+class Hotel < ActiveRecord::Base
+ has_many :departments
+ has_many :chefs, through: :departments
+ has_many :cake_designers, source_type: 'CakeDesigner', source: :employable, through: :chefs
+ has_many :drink_designers, source_type: 'DrinkDesigner', source: :employable, through: :chefs
+end
diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb
index cc47c7bc18..72095f9236 100644
--- a/activerecord/test/models/member.rb
+++ b/activerecord/test/models/member.rb
@@ -2,7 +2,6 @@ class Member < ActiveRecord::Base
has_one :current_membership
has_one :selected_membership
has_one :membership
- has_many :fellow_members, :through => :club, :source => :members
has_one :club, :through => :current_membership
has_one :selected_club, :through => :selected_membership, :source => :club
has_one :favourite_club, -> { where "memberships.favourite = ?", true }, :through => :membership, :source => :club
diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb
index bcbb7e42c5..df7167ee93 100644
--- a/activerecord/test/models/membership.rb
+++ b/activerecord/test/models/membership.rb
@@ -8,6 +8,11 @@ class CurrentMembership < Membership
belongs_to :club
end
+class SuperMembership < Membership
+ belongs_to :member, -> { order('members.id DESC') }
+ belongs_to :club
+end
+
class SelectedMembership < Membership
def self.default_scope
select("'1' as foo")
diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb
index 6384b4c801..c441be2bef 100644
--- a/activerecord/test/models/movie.rb
+++ b/activerecord/test/models/movie.rb
@@ -1,5 +1,3 @@
class Movie < ActiveRecord::Base
- def self.primary_key
- "movieid"
- end
+ self.primary_key = "movieid"
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 25fb2cffe2..faf539a562 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -65,6 +65,9 @@ class Post < ActiveRecord::Base
has_many :author_favorites, :through => :author
has_many :author_categorizations, :through => :author, :source => :categorizations
has_many :author_addresses, :through => :author
+ has_many :author_address_extra_with_address,
+ through: :author_with_address,
+ source: :author_address_extra
has_many :comments_with_interpolated_conditions,
->(p) { where "#{"#{p.aliased_table_name}." rescue ""}body = ?", 'Thank you for the welcome' },
@@ -130,7 +133,6 @@ class Post < ActiveRecord::Base
has_many :secure_readers
has_many :readers_with_person, -> { includes(:person) }, :class_name => "Reader"
has_many :people, :through => :readers
- has_many :secure_people, :through => :secure_readers
has_many :single_people, :through => :readers
has_many :people_with_callbacks, :source=>:person, :through => :readers,
:before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) },
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index c094b726b4..7f42a4b1f8 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -1,7 +1,6 @@
class Project < ActiveRecord::Base
has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' }
has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer"
- has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer"
has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer'
has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer"
has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer"
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 17035bf338..40c8e97fc2 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -34,7 +34,6 @@ class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :approved_replies, -> { approved }, class_name: 'Reply', foreign_key: "parent_id", counter_cache: 'replies_count'
- has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 75711673a7..88a686d436 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -787,6 +787,22 @@ ActiveRecord::Schema.define do
t.string 'from'
end
+ create_table :hotels, force: true do |t|
+ end
+ create_table :departments, force: true do |t|
+ t.integer :hotel_id
+ end
+ create_table :cake_designers, force: true do |t|
+ end
+ create_table :drink_designers, force: true do |t|
+ end
+ create_table :chefs, force: true do |t|
+ t.integer :employable_id
+ t.string :employable_type
+ t.integer :department_id
+ end
+
+
except 'SQLite' do
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, :force => true do |t|
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
index e9ddeb32cf..b7aff4f47d 100644
--- a/activerecord/test/schema/sqlite_specific_schema.rb
+++ b/activerecord/test/schema/sqlite_specific_schema.rb
@@ -1,9 +1,6 @@
ActiveRecord::Schema.define do
- # For sqlite 3.1.0+, make a table with an autoincrement column
- if supports_autoincrement?
- create_table :table_with_autoincrement, :force => true do |t|
- t.column :name, :string
- end
+ create_table :table_with_autoincrement, :force => true do |t|
+ t.column :name, :string
end
execute "DROP TABLE fk_test_has_fk" rescue nil
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index f15297f279..2d934f8bc4 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,62 @@
+* Calling ActiveSupport::JSON.decode with unsupported options now raises an error.
+
+ *Godfrey Chan*
+
+* Support :unless_exist in FileStore
+
+ *Michael Grosser*
+
+* Fix `slice!` deleting the default value of the hash.
+
+ *Antonio Santos*
+
+* `require_dependency` accepts objects that respond to `to_path`, in
+ particular `Pathname` instances.
+
+ *Benjamin Fleischer*
+
+* Disable the ability to iterate over Range of AS::TimeWithZone
+ due to significant performance issues.
+
+ *Bogdan Gusiev*
+
+* Allow attaching event subscribers to ActiveSupport::Notifications namespaces
+ before they're defined. Essentially, this means instead of this:
+
+ class JokeSubscriber < ActiveSupport::Subscriber
+ def sql(event)
+ puts "A rabbi and a priest walk into a bar..."
+ end
+
+ # This call needs to happen *after* defining the methods.
+ attach_to "active_record"
+ end
+
+ You can do this:
+
+ class JokeSubscriber < ActiveSupport::Subscriber
+ # This is much easier to read!
+ attach_to "active_record"
+
+ def sql(event)
+ puts "A rabbi and a priest walk into a bar..."
+ end
+ end
+
+ This should make it easier to read and understand these subscribers.
+
+ *Daniel Schierbeck*
+
+* Add `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day` methods.
+
+ Also added `midday`, `noon`, `at_midday`, `at_noon` and `at_middle_of_day` as aliases.
+
+ *Anatoli Makarevich*
+
+* Fix ActiveSupport::Cache::FileStore#cleanup to no longer rely on missing each_key method.
+
+ *Murray Steele*
+
* Ensure that autoloaded constants in all-caps nestings are marked as
autoloaded.
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 5fefa429df..f39e89b7d0 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -28,12 +28,6 @@ module ActiveSupport
def initialize
@ucd = Unicode::UnicodeDatabase.new
-
- default = Codepoint.new
- default.combining_class = 0
- default.uppercase_mapping = 0
- default.lowercase_mapping = 0
- @ucd.codepoints = Hash.new(default)
end
def parse_codepoints(line)
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 308a1b2cd9..c5633d902f 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -234,7 +234,7 @@ module ActiveSupport
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
# Yes, this process is extending the time for a stale value by another few
# seconds. Because of extended life of the previous cache, other processes
- # will continue to use slightly stale data for a just a big longer. In the
+ # 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.
# The key is to keep <tt>:race_condition_ttl</tt> small.
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 472f23c1c5..5cd6065077 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -33,7 +33,8 @@ module ActiveSupport
# Premptively iterates through all stored keys and removes the ones which have expired.
def cleanup(options = nil)
options = merged_options(options)
- each_key(options) do |key|
+ search_dir(cache_path) do |fname|
+ key = file_path_key(fname)
entry = read_entry(key, options)
delete_entry(key, options) if entry && entry.expired?
end
@@ -96,6 +97,7 @@ module ActiveSupport
def write_entry(key, entry, options)
file_name = key_file_path(key)
+ return false if options[:unless_exist] && File.exist?(file_name)
ensure_cache_path(File.dirname(file_name))
File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
true
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 5c738572a8..c3aac31323 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -574,7 +574,7 @@ module ActiveSupport
#
# set_callback :save, :before_meth
#
- # The callback can specified as a symbol naming an instance method; as a
+ # The callback can be specified as a symbol naming an instance method; as a
# proc, lambda, or block; as a string to be instance evaluated; or as an
# object that responds to a certain method determined by the <tt>:scope</tt>
# argument to +define_callback+.
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 83038f9da5..f2a221c396 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -118,7 +118,10 @@ class Class
end
private
- def singleton_class?
- ancestors.first != self
+
+ unless respond_to?(:singleton_class?)
+ def singleton_class?
+ ancestors.first != self
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 9fa9b3dac4..8ad600b171 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -26,6 +26,8 @@ class Hash
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*self.keys - keys)
hash = slice(*keys)
+ hash.default = default
+ hash.default_proc = default_proc if default_proc
replace(hash)
omit
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index ec2157221f..f4f9152d6a 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -8,7 +8,7 @@ require 'active_support/core_ext/object/inclusion'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
-require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/object/json'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/object/with_options'
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
new file mode 100644
index 0000000000..8586d7eb16
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -0,0 +1,217 @@
+# Hack to load json gem first so we can overwrite its to_json.
+require 'json'
+require 'bigdecimal'
+
+# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
+# their default behavior. That said, we need to define the basic to_json method in all of them,
+# otherwise they will always use to_json gem implementation, which is backwards incompatible in
+# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
+# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
+[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
+ klass.class_eval do
+ # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
+ def to_json(options = nil)
+ ActiveSupport::JSON.encode(self, options)
+ end
+ end
+end
+
+class Object
+ def as_json(options = nil) #:nodoc:
+ if respond_to?(:to_hash)
+ to_hash
+ else
+ instance_values
+ end
+ end
+end
+
+class Struct #:nodoc:
+ def as_json(options = nil)
+ Hash[members.zip(values)]
+ end
+end
+
+class TrueClass
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ to_s
+ end
+end
+
+class FalseClass
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ to_s
+ end
+end
+
+class NilClass
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ 'null'
+ end
+end
+
+class String
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ encoder.escape(self)
+ end
+end
+
+class Symbol
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
+class Numeric
+ def as_json(options = nil) #:nodoc:
+ self
+ end
+
+ def encode_json(encoder) #:nodoc:
+ to_s
+ end
+end
+
+class Float
+ # Encoding Infinity or NaN to JSON should return "null". The default returns
+ # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
+ def as_json(options = nil) #:nodoc:
+ finite? ? self : nil
+ end
+end
+
+class BigDecimal
+ # A BigDecimal would be naturally represented as a JSON number. Most libraries,
+ # however, parse non-integer JSON numbers directly as floats. Clients using
+ # those libraries would get in general a wrong number and no way to recover
+ # other than manually inspecting the string with the JSON code itself.
+ #
+ # That's why a JSON string is returned. The JSON literal is not numeric, but
+ # if the other end knows by contract that the data is supposed to be a
+ # BigDecimal, it still has the chance to post-process the string and get the
+ # real value.
+ #
+ # Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to
+ # override this behavior.
+ def as_json(options = nil) #:nodoc:
+ if finite?
+ ActiveSupport.encode_big_decimal_as_string ? to_s : self
+ else
+ nil
+ end
+ end
+end
+
+class Regexp
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
+module Enumerable
+ def as_json(options = nil) #:nodoc:
+ to_a.as_json(options)
+ end
+end
+
+class Range
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
+class Array
+ def as_json(options = nil) #:nodoc:
+ # use encoder as a proxy to call as_json on all elements, to protect from circular references
+ encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
+ map { |v| encoder.as_json(v, options) }
+ end
+
+ def encode_json(encoder) #:nodoc:
+ # we assume here that the encoder has already run as_json on self and the elements, so we run encode_json directly
+ "[#{map { |v| v.encode_json(encoder) } * ','}]"
+ end
+end
+
+class Hash
+ def as_json(options = nil) #:nodoc:
+ # create a subset of the hash by applying :only or :except
+ subset = if options
+ if attrs = options[:only]
+ slice(*Array(attrs))
+ elsif attrs = options[:except]
+ except(*Array(attrs))
+ else
+ self
+ end
+ else
+ self
+ end
+
+ # use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
+ encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
+ Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
+ end
+
+ def encode_json(encoder) #:nodoc:
+ # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be
+ # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields);
+
+ # on the other hand, we need to run as_json on the elements, because the model representation may contain fields
+ # like Time/Date in their original (not jsonified) form, etc.
+
+ "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}"
+ end
+end
+
+class Time
+ def as_json(options = nil) #:nodoc:
+ if ActiveSupport.use_standard_json_time_format
+ xmlschema
+ else
+ %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
+ end
+ end
+end
+
+class Date
+ def as_json(options = nil) #:nodoc:
+ if ActiveSupport.use_standard_json_time_format
+ strftime("%Y-%m-%d")
+ else
+ strftime("%Y/%m/%d")
+ end
+ end
+end
+
+class DateTime
+ def as_json(options = nil) #:nodoc:
+ if ActiveSupport.use_standard_json_time_format
+ xmlschema
+ else
+ strftime('%Y/%m/%d %H:%M:%S %z')
+ end
+ end
+end
+
+class Process::Status
+ def as_json(options = nil)
+ { :exitstatus => exitstatus, :pid => pid }
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb
index 83cc8066e7..3dcae6fc7f 100644
--- a/activesupport/lib/active_support/core_ext/object/to_json.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_json.rb
@@ -1,27 +1,5 @@
-# Hack to load json gem first so we can overwrite its to_json.
-begin
- require 'json'
-rescue LoadError
-end
+ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/object/to_json`. ' \
+ 'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \
+ 'instead.'
-# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
-# their default behavior. That said, we need to define the basic to_json method in all of them,
-# otherwise they will always use to_json gem implementation, which is backwards incompatible in
-# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
-# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
-[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
- klass.class_eval do
- # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
- def to_json(options = nil)
- ActiveSupport::JSON.encode(self, options)
- end
- end
-end
-
-module Process
- class Status
- def as_json(options = nil)
- { :exitstatus => exitstatus, :pid => pid }
- end
- end
-end
+require 'active_support/core_ext/object/json'
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 534bbe3c42..48190e1e66 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -47,7 +47,7 @@ class Object
end
# Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
- # does not implemented the tried method.
+ # does not implement the tried method.
def try!(*a, &b)
if a.empty? && block_given?
yield self
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index 1d8b1ede5a..9368e81235 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/range/conversions'
require 'active_support/core_ext/range/include_range'
require 'active_support/core_ext/range/overlaps'
+require 'active_support/core_ext/range/each'
diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb
new file mode 100644
index 0000000000..d51ea2e944
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/range/each.rb
@@ -0,0 +1,24 @@
+require 'active_support/core_ext/module/aliasing'
+require 'active_support/core_ext/object/acts_like'
+
+class Range #:nodoc:
+
+ def each_with_time_with_zone(&block)
+ ensure_iteration_allowed
+ each_without_time_with_zone(&block)
+ end
+ alias_method_chain :each, :time_with_zone
+
+ def step_with_time_with_zone(n = 1, &block)
+ ensure_iteration_allowed
+ step_without_time_with_zone(n, &block)
+ end
+ alias_method_chain :step, :time_with_zone
+
+ private
+ def ensure_iteration_allowed
+ if first.acts_like?(:time)
+ raise TypeError, "can't iterate from #{first.class}"
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index db9f5d4baa..19d4ff51d7 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -8,6 +8,7 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/qualified_const'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
@@ -198,9 +199,19 @@ module ActiveSupport #:nodoc:
Dependencies.require_or_load(file_name)
end
+ # Interprets a file using <tt>mechanism</tt> and marks its defined
+ # constants as autoloaded. <tt>file_name</tt> can be either a string or
+ # respond to <tt>to_path</tt>.
+ #
+ # Use this method in code that absolutely needs a certain constant to be
+ # defined at that point. A typical use case is to make constant name
+ # resolution deterministic for constants with the same relative name in
+ # different namespaces whose evaluation would depend on load order
+ # otherwise.
def require_dependency(file_name, message = "No such file to load -- %s")
+ file_name = file_name.to_path if file_name.respond_to?(:to_path)
unless file_name.is_a?(String)
- raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
+ raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}"
end
Dependencies.depend_on(file_name, message)
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 00727ef73c..87b6407038 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -70,12 +70,10 @@ module ActiveSupport
alias :until :ago
def inspect #:nodoc:
- val_for = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }
- [:years, :months, :days, :minutes, :seconds].
- select {|unit| val_for[unit].nonzero?}.
- tap {|units| units << :seconds if units.empty?}.
- map {|unit| [unit, val_for[unit]]}.
- map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
+ parts.
+ reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
+ sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
+ map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
to_sentence(:locale => :en)
end
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 30833a4cb1..315c76199a 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -13,8 +13,15 @@ module ActiveSupport
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
- def decode(json, proc = nil, options = {})
- data = ::JSON.load(json, proc, options)
+ def decode(json, options = {})
+ if options.present?
+ raise ArgumentError, "In Rails 4.1, ActiveSupport::JSON.decode no longer " \
+ "accepts an options hash for MultiJSON. MultiJSON reached its end of life " \
+ "and has been removed."
+ end
+
+ data = ::JSON.parse(json, quirks_mode: true)
+
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 77b5d8d227..5f58068617 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,9 +1,8 @@
#encoding: us-ascii
-require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/object/json'
require 'active_support/core_ext/module/delegation'
-require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
@@ -148,197 +147,3 @@ module ActiveSupport
end
end
end
-
-class Object
- def as_json(options = nil) #:nodoc:
- if respond_to?(:to_hash)
- to_hash
- else
- instance_values
- end
- end
-end
-
-class Struct #:nodoc:
- def as_json(options = nil)
- Hash[members.zip(values)]
- end
-end
-
-class TrueClass
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
-end
-
-class FalseClass
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
-end
-
-class NilClass
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- 'null'
- end
-end
-
-class String
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- encoder.escape(self)
- end
-end
-
-class Symbol
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
-
-class Numeric
- def as_json(options = nil) #:nodoc:
- self
- end
-
- def encode_json(encoder) #:nodoc:
- to_s
- end
-end
-
-class Float
- # Encoding Infinity or NaN to JSON should return "null". The default returns
- # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
- def as_json(options = nil) #:nodoc:
- finite? ? self : nil
- end
-end
-
-class BigDecimal
- # A BigDecimal would be naturally represented as a JSON number. Most libraries,
- # however, parse non-integer JSON numbers directly as floats. Clients using
- # those libraries would get in general a wrong number and no way to recover
- # other than manually inspecting the string with the JSON code itself.
- #
- # That's why a JSON string is returned. The JSON literal is not numeric, but
- # if the other end knows by contract that the data is supposed to be a
- # BigDecimal, it still has the chance to post-process the string and get the
- # real value.
- #
- # Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to
- # override this behavior.
- def as_json(options = nil) #:nodoc:
- if finite?
- ActiveSupport.encode_big_decimal_as_string ? to_s : self
- else
- nil
- end
- end
-end
-
-class Regexp
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
-
-module Enumerable
- def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
- end
-end
-
-class Range
- def as_json(options = nil) #:nodoc:
- to_s
- end
-end
-
-class Array
- def as_json(options = nil) #:nodoc:
- # use encoder as a proxy to call as_json on all elements, to protect from circular references
- encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- map { |v| encoder.as_json(v, options) }
- end
-
- def encode_json(encoder) #:nodoc:
- # we assume here that the encoder has already run as_json on self and the elements, so we run encode_json directly
- "[#{map { |v| v.encode_json(encoder) } * ','}]"
- end
-end
-
-class Hash
- def as_json(options = nil) #:nodoc:
- # create a subset of the hash by applying :only or :except
- subset = if options
- if attrs = options[:only]
- slice(*Array(attrs))
- elsif attrs = options[:except]
- except(*Array(attrs))
- else
- self
- end
- else
- self
- end
-
- # use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
- encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
- end
-
- def encode_json(encoder) #:nodoc:
- # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be
- # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields);
-
- # on the other hand, we need to run as_json on the elements, because the model representation may contain fields
- # like Time/Date in their original (not jsonified) form, etc.
-
- "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}"
- end
-end
-
-class Time
- def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
- xmlschema
- else
- %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
- end
- end
-end
-
-class Date
- def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
- strftime("%Y-%m-%d")
- else
- strftime("%Y/%m/%d")
- end
- end
-end
-
-class DateTime
- def as_json(options = nil) #:nodoc:
- if ActiveSupport.use_standard_json_time_format
- xmlschema
- else
- strftime('%Y/%m/%d %H:%M:%S %z')
- end
- end
-end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index a42e7f6542..3c0cf9f137 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -56,11 +56,10 @@ module ActiveSupport #:nodoc:
# Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
+ result = @wrapped_string.__send__(method, *args, &block)
if method.to_s =~ /!$/
- result = @wrapped_string.__send__(method, *args, &block)
self if result
else
- result = @wrapped_string.__send__(method, *args, &block)
result.kind_of?(String) ? chars(result) : result
end
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 04e6b71580..1845c6ae38 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -287,6 +287,13 @@ module ActiveSupport
class Codepoint
attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
+ # Initializing Codepoint object with default values
+ def initialize
+ @combining_class = 0
+ @uppercase_mapping = 0
+ @lowercase_mapping = 0
+ end
+
def swapcase_mapping
uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 99fe03e6d0..8f5fa646e8 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -107,21 +107,18 @@ module ActiveSupport
end
class Timed < Evented
- def initialize(pattern, delegate)
- @timestack = []
- super
- end
-
def publish(name, *args)
@delegate.call name, *args
end
def start(name, id, payload)
- @timestack.push Time.now
+ timestack = Thread.current[:_timestack] ||= []
+ timestack.push Time.now
end
def finish(name, id, payload)
- started = @timestack.pop
+ timestack = Thread.current[:_timestack]
+ started = timestack.pop
@delegate.call(name, started, Time.now, id, payload)
end
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 54cff6d394..e0151baa36 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -108,7 +108,7 @@ module ActiveSupport
DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto }
-
+ INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
# Formats a +number+ into a US phone number (e.g., (555)
@@ -460,7 +460,7 @@ module ActiveSupport
# See <tt>number_to_human_size</tt> if you want to print a file
# size.
#
- # You can also define you own unit-quantifier names if you want
+ # You can also define your own unit-quantifier names if you want
# to use other decimal units (eg.: 1500 becomes "1.5
# kilometers", 0.150 becomes "150 milliliters", etc). You may
# define a wide range of unit quantifiers, even fractional ones
@@ -561,8 +561,6 @@ module ActiveSupport
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
- inverted_du = DECIMAL_UNITS.invert
-
units = options.delete :units
unit_exponents = case units
when Hash
@@ -573,7 +571,7 @@ module ActiveSupport
translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true)
else
raise ArgumentError, ":units must be a Hash or String translation scope."
- end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e}
+ end.keys.map!{|e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by!{|e| -e}
number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 34c6f900c1..f2966f23fc 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -31,18 +31,41 @@ module ActiveSupport
# Attach the subscriber to a namespace.
def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications)
+ @namespace = namespace
+ @subscriber = subscriber
+ @notifier = notifier
+
subscribers << subscriber
+ # Add event subscribers for all existing methods on the class.
subscriber.public_methods(false).each do |event|
- next if %w{ start finish }.include?(event.to_s)
+ add_event_subscriber(event)
+ end
+ end
- notifier.subscribe("#{event}.#{namespace}", subscriber)
+ # Adds event subscribers for all new methods added to the class.
+ def method_added(event)
+ # Only public methods are added as subscribers, and only if a notifier
+ # has been set up. This means that subscribers will only be set up for
+ # classes that call #attach_to.
+ if public_method_defined?(event) && notifier
+ add_event_subscriber(event)
end
end
def subscribers
@@subscribers ||= []
end
+
+ protected
+
+ attr_reader :subscriber, :notifier, :namespace
+
+ def add_event_subscriber(event)
+ return if %w{ start finish }.include?(event.to_s)
+
+ notifier.subscribe("#{event}.#{namespace}", subscriber)
+ end
end
def initialize
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 2f27aff2bd..5e24118d34 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -22,7 +22,7 @@ module Minitest # :nodoc:
def self.__run reporter, options # :nodoc:
# FIXME: MT5's runnables is not ordered. This is needed because
- # we have have tests have cross-class order-dependent bugs.
+ # we have tests with cross-class order-dependent bugs.
suites = Runnable.runnables.sort_by { |ts| ts.name.to_s }
parallel, serial = suites.partition { |s| s.test_order == :parallel }
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index df570d485a..51007402a1 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -709,6 +709,25 @@ class FileStoreTest < ActiveSupport::TestCase
@cache.send(:read_entry, "winston", {})
assert @buffer.string.present?
end
+
+ def test_cleanup_removes_all_expired_entries
+ time = Time.now
+ @cache.write('foo', 'bar', expires_in: 10)
+ @cache.write('baz', 'qux')
+ @cache.write('quux', 'corge', expires_in: 20)
+ Time.stubs(:now).returns(time + 15)
+ @cache.cleanup
+ assert_not @cache.exist?('foo')
+ assert @cache.exist?('baz')
+ assert @cache.exist?('quux')
+ end
+
+ def test_write_with_unless_exist
+ assert_equal true, @cache.write(1, "aaaaaaaaaa")
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ @cache.write(1, nil)
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
+ end
end
class MemoryStoreTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index a5987044b9..b386e55d6c 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'bigdecimal'
require 'active_support/core_ext/big_decimal'
class BigDecimalTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 5e3987265b..ed267cf4b9 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -27,6 +27,7 @@ class DurationTest < ActiveSupport::TestCase
def test_equals
assert 1.day == 1.day
assert 1.day == 1.day.to_i
+ assert 1.day.to_i == 1.day
assert !(1.day == 'foo')
end
@@ -37,6 +38,8 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '6 months and -2 days', (6.months - 2.days).inspect
assert_equal '10 seconds', 10.seconds.inspect
assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect
+ assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect
+ assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect
assert_equal '7 days', 1.week.inspect
assert_equal '14 days', 1.fortnight.inspect
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 2d0c56bef5..b059bc3e89 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -781,6 +781,24 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 'bender', slice['login']
end
+ def test_slice_bang_does_not_override_default
+ hash = Hash.new(0)
+ hash.update(a: 1, b: 2)
+
+ hash.slice!(:a)
+
+ assert_equal 0, hash[:c]
+ end
+
+ def test_slice_bang_does_not_override_default_proc
+ hash = Hash.new { |h, k| h[k] = [] }
+ hash.update(a: 1, b: 2)
+
+ hash.slice!(:a)
+
+ assert_equal [], hash[:c]
+ end
+
def test_extract
original = {:a => 1, :b => 2, :c => 3, :d => 4}
expected = {:a => 1, :b => 2}
diff --git a/activesupport/test/core_ext/object/json_test.rb b/activesupport/test/core_ext/object/json_test.rb
new file mode 100644
index 0000000000..d3d31530df
--- /dev/null
+++ b/activesupport/test/core_ext/object/json_test.rb
@@ -0,0 +1,9 @@
+require 'abstract_unit'
+
+class JsonTest < ActiveSupport::TestCase
+ # See activesupport/test/json/encoding_test.rb for JSON encoding tests
+
+ def test_deprecated_require_to_json_rb
+ assert_deprecated { require 'active_support/core_ext/object/to_json' }
+ end
+end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 2f8723a074..854b0a38bd 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -90,4 +90,26 @@ class RangeTest < ActiveSupport::TestCase
time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00)
assert !time_range_1.overlaps?(time_range_2)
end
+
+ def test_each_on_time_with_zone
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30))
+ assert_raises TypeError do
+ ((twz - 1.hour)..twz).each {}
+ end
+ end
+
+ def test_step_on_time_with_zone
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30))
+ assert_raises TypeError do
+ ((twz - 1.hour)..twz).step(1) {}
+ end
+ end
+
+ def test_include_on_time_with_zone
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30))
+ assert_raises TypeError do
+ ((twz - 1.hour)..twz).include?(twz)
+ end
+ end
+
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 2537af0394..3cb66d4eec 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -11,13 +11,6 @@ require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/indent'
-module Ace
- module Base
- class Case
- end
- end
-end
-
class StringInflectionsTest < ActiveSupport::TestCase
include InflectorTestCases
include ConstantizeTestCases
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
index 54d2dcd8dd..6a7c6e0604 100644
--- a/activesupport/test/core_ext/thread_test.rb
+++ b/activesupport/test/core_ext/thread_test.rb
@@ -72,17 +72,4 @@ class ThreadExt < ActiveSupport::TestCase
end
end
- def test_thread_variable_security
- rubinius_skip "$SAFE is not supported on Rubinius."
-
- t = Thread.new { sleep }
-
- assert_raises(SecurityError) do
- Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join
- end
-
- assert_raises(SecurityError) do
- Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join
- end
- end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 9b84e75902..2392b71960 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -35,6 +35,17 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal expected.path, e.path
end
+ def test_require_dependency_accepts_an_object_which_implements_to_path
+ o = Object.new
+ def o.to_path; 'dependencies/service_one'; end
+ assert_nothing_raised {
+ require_dependency o
+ }
+ assert defined?(ServiceOne)
+ ensure
+ remove_constants(:ServiceOne)
+ end
+
def test_tracking_loaded_files
require_dependency 'dependencies/service_one'
require_dependency 'dependencies/service_two'
diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb
index 74669aaca1..00b449af51 100644
--- a/activesupport/test/descendants_tracker_without_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb
@@ -4,4 +4,14 @@ require 'descendants_tracker_test_cases'
class DescendantsTrackerWithoutAutoloadingTest < ActiveSupport::TestCase
include DescendantsTrackerTestCases
+
+ # Regression test for #8422. https://github.com/rails/rails/issues/8442
+ def test_clear_without_autoloaded_singleton_parent
+ mark_as_autoloaded do
+ parent_instance = Parent.new
+ parent_instance.singleton_class.descendants
+ ActiveSupport::DescendantsTracker.clear
+ assert !ActiveSupport::DescendantsTracker.class_variable_get(:@@direct_descendants).key?(parent_instance.singleton_class)
+ end
+ end
end
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 99c5f2d1ec..07d7e530ca 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -4,6 +4,12 @@ require 'active_support/json'
require 'active_support/time'
class TestJSONDecoding < ActiveSupport::TestCase
+ class Foo
+ def self.json_create(object)
+ "Foo"
+ end
+ end
+
TESTS = {
%q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}},
%q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}},
@@ -52,7 +58,17 @@ class TestJSONDecoding < ActiveSupport::TestCase
# tests escaping of "\n" char with Yaml backend
%q({"a":"\n"}) => {"a"=>"\n"},
%q({"a":"\u000a"}) => {"a"=>"\n"},
- %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}
+ %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"},
+ # prevent json unmarshalling
+ %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"},
+ # json "fragments" - these are invalid JSON, but ActionPack relies on this
+ %q("a string") => "a string",
+ %q(1.1) => 1.1,
+ %q(1) => 1,
+ %q(-1) => -1,
+ %q(true) => true,
+ %q(false) => false,
+ %q(null) => nil
}
TESTS.each_with_index do |(json, expected), index|
@@ -76,7 +92,14 @@ class TestJSONDecoding < ActiveSupport::TestCase
end
def test_failed_json_decoding
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) }
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) }
assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) }
+ assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) }
+ end
+
+ def test_cannot_pass_unsupported_options
+ assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) }
end
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index ed1326705c..d549113ff4 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,4 +1,5 @@
# encoding: utf-8
+require 'securerandom'
require 'abstract_unit'
require 'active_support/core_ext/string/inflections'
require 'active_support/json'
@@ -96,6 +97,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ def test_process_status
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
+ # creating a child process, hence this to populate $?
+ system("not_a_real_program_#{SecureRandom.hex}")
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
+ end
+
def test_hash_encoding
assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b)
assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1)
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index c3fe89de4b..0b54026c64 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
require 'active_support/json'
-require 'active_support/core_ext/object/to_json'
+require 'active_support/core_ext/object/json'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/array/extract_options'
diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb
new file mode 100644
index 0000000000..253411aa3d
--- /dev/null
+++ b/activesupport/test/subscriber_test.rb
@@ -0,0 +1,40 @@
+require 'abstract_unit'
+require 'active_support/subscriber'
+
+class TestSubscriber < ActiveSupport::Subscriber
+ attach_to :doodle
+
+ cattr_reader :event
+
+ def self.clear
+ @@event = nil
+ end
+
+ def open_party(event)
+ @@event = event
+ end
+
+ private
+
+ def private_party(event)
+ @@event = event
+ end
+end
+
+class SubscriberTest < ActiveSupport::TestCase
+ def setup
+ TestSubscriber.clear
+ end
+
+ def test_attaches_subscribers
+ ActiveSupport::Notifications.instrument("open_party.doodle")
+
+ assert_equal "open_party.doodle", TestSubscriber.event.name
+ end
+
+ def test_does_not_attach_private_methods
+ ActiveSupport::Notifications.instrument("private_party.doodle")
+
+ assert_nil TestSubscriber.event
+ end
+end
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index ce91c443e1..e0f85f4e7c 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -3,7 +3,6 @@ require 'abstract_unit'
require 'active_support/inflector/transliterate'
class TransliterateTest < ActiveSupport::TestCase
-
def test_transliterate_should_not_change_ascii_chars
(0..127).each do |byte|
char = [byte].pack("U")
@@ -24,12 +23,13 @@ class TransliterateTest < ActiveSupport::TestCase
def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8
char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS
I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}})
- I18n.locale = :de
+ default_locale, I18n.locale = I18n.locale, :de
assert_equal "ue", ActiveSupport::Inflector.transliterate(char)
+ ensure
+ I18n.locale = default_locale
end
def test_transliterate_should_allow_a_custom_replacement_char
assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*")
end
-
end
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index a025279e16..d992028323 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -106,7 +106,11 @@ module XmlMiniTest
module Nokogiri end
setup do
- @xml = ActiveSupport::XmlMini
+ @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend
+ end
+
+ teardown do
+ ActiveSupport::XmlMini.backend = @default_backend
end
test "#with_backend should switch backend and then switch back" do
@@ -135,7 +139,11 @@ module XmlMiniTest
module LibXML end
setup do
- @xml = ActiveSupport::XmlMini
+ @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend
+ end
+
+ teardown do
+ ActiveSupport::XmlMini.backend = @default_backend
end
test "#with_backend should be thread-safe" do
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index afa695d445..38e407b198 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -2,4 +2,8 @@
*Sıtkı Bağdat*
+* Added the Rails maintenance policy to the guides.
+
+ *Matias Korhonen*
+
Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 63fbe27aed..a8868a1877 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -25,7 +25,7 @@ class Comment < ActiveRecord::Base
belongs_to :post
end
-class BugTest < Minitest::Test
+class BugTest < MiniTest::Unit::TestCase
def test_association_stuff
post = Post.create!
post.comments << Comment.create!
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
index 3d57012901..eef6180804 100644
--- a/guides/code/getting_started/Gemfile
+++ b/guides/code/getting_started/Gemfile
@@ -31,7 +31,7 @@ end
gem 'jbuilder', '~> 1.2'
# To use ActiveModel has_secure_password
-# gem 'bcrypt-ruby', '~> 3.1.0'
+# gem 'bcrypt-ruby', '~> 3.1.2'
# Use unicorn as the app server
# gem 'unicorn'
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index d398cd680c..cf9d694de7 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -73,8 +73,6 @@ You can see an example of how that works at [Rails Upgrade is now an Official Pl
Aside from Rails Upgrade tool, if you need more help, there are people on IRC and [rubyonrails-talk](http://groups.google.com/group/rubyonrails-talk) that are probably doing the same thing, possibly hitting the same issues. Be sure to blog your own experiences when upgrading so others can benefit from your knowledge!
-More information - [The Path to Rails 3: Approaching the upgrade](http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade)
-
Creating a Rails 3.0 application
--------------------------------
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index dc4d942671..babdc5050e 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -21,7 +21,7 @@ If you're upgrading an existing application, it's a great idea to have good test
Rails 3.2 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.2 is also compatible with Ruby 1.9.2.
-TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing.
+TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing.
### What to update in your apps
@@ -137,7 +137,7 @@ Railties
* Update `Rails::Rack::Logger` middleware to apply any tags set in `config.log_tags` to `ActiveSupport::TaggedLogging`. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications.
-* Default options to `rails new` can be set in `~/.railsrc`. You can specify extra command-line arguments to be used every time 'rails new' runs in the `.railsrc` configuration file in your home directory.
+* Default options to `rails new` can be set in `~/.railsrc`. You can specify extra command-line arguments to be used every time `rails new` runs in the `.railsrc` configuration file in your home directory.
* Add an alias `d` for `destroy`. This works for engines too.
@@ -185,9 +185,9 @@ Action Pack
end
```
- Rails will use 'layouts/single_car' when a request comes in :show action, and use 'layouts/application' (or 'layouts/cars', if exists) when a request comes in for any other actions.
+ Rails will use `layouts/single_car` when a request comes in `:show` action, and use `layouts/application` (or `layouts/cars`, if exists) when a request comes in for any other actions.
-* form\_for is changed to use "#{action}\_#{as}" as the css class and id if `:as` option is provided. Earlier versions used "#{as}\_#{action}".
+* `form\_for` is changed to use `#{action}\_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}\_#{action}`.
* `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`.
@@ -219,7 +219,7 @@ Action Pack
* MIME type entries for PDF, ZIP and other formats were added.
-* Allow fresh_when/stale? to take a record instead of an options hash.
+* Allow `fresh_when/stale?` to take a record instead of an options hash.
* Changed log level of warning for missing CSRF token from `:debug` to `:warn`.
@@ -227,7 +227,7 @@ Action Pack
#### Deprecations
-* Deprecated implied layout lookup in controllers whose parent had a explicit layout set:
+* Deprecated implied layout lookup in controllers whose parent had an explicit layout set:
```ruby
class ApplicationController
@@ -254,7 +254,7 @@ Action Pack
* Added `ActionDispatch::RequestId` middleware that'll make a unique X-Request-Id header available to the response and enables the `ActionDispatch::Request#uuid` method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog.
-* The `ShowExceptions` middleware now accepts a exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in `env["action_dispatch.exception"]` and with the `PATH_INFO` rewritten to the status code.
+* The `ShowExceptions` middleware now accepts an exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in `env["action_dispatch.exception"]` and with the `PATH_INFO` rewritten to the status code.
* Allow rescue responses to be configured through a railtie as in `config.action_dispatch.rescue_responses`.
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index ff4d30b4c0..c4ca1e921f 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -116,7 +116,7 @@ Documentation
Railties
--------
-Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/railties/CHANGELOG.md) for detailed changes.
### Notable changes
@@ -139,7 +139,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt
Action Mailer
-------------
-Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionmailer/CHANGELOG.md) for detailed changes.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/actionmailer/CHANGELOG.md) for detailed changes.
### Notable changes
@@ -148,7 +148,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actio
Active Model
------------
-Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activemodel/CHANGELOG.md) for detailed changes.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for detailed changes.
### Notable changes
@@ -161,7 +161,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ
Active Support
--------------
-Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activesupport/CHANGELOG.md) for detailed changes.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activesupport/CHANGELOG.md) for detailed changes.
### Notable changes
@@ -190,20 +190,20 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ
* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead.
-* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1.
+* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1.
* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals.
-* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols).
+* Deprecates the compatibility method `Module#local_constant_names`, use `Module#local_constants` instead (which returns symbols).
-* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library.
+* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger from Ruby standard library.
* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?`
Action Pack
-----------
-Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionpack/CHANGELOG.md) for detailed changes.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for detailed changes.
### Notable changes
@@ -215,7 +215,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actio
Active Record
-------------
-Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activerecord/CHANGELOG.md) for detailed changes.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for detailed changes.
### Notable changes
@@ -266,9 +266,9 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ
* `find_all_by_...` can be rewritten using `where(...)`.
* `find_last_by_...` can be rewritten using `where(...).last`.
* `scoped_by_...` can be rewritten using `where(...)`.
- * `find_or_initialize_by_...` can be rewritten using `where(...).first_or_initialize`.
- * `find_or_create_by_...` can be rewritten using `find_or_create_by(...)` or `where(...).first_or_create`.
- * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)` or `where(...).first_or_create!`.
+ * `find_or_initialize_by_...` can be rewritten using `find_or_initialize_by(...)`.
+ * `find_or_create_by_...` can be rewritten using `find_or_create_by(...)`.
+ * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)`.
Credits
-------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 9210c40c17..0a0a958e30 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -15,7 +15,7 @@
</p>
<% end %>
<p>
- The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.13/">http://guides.rubyonrails.org/v3.2.13/</a>.
+ The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.14/">http://guides.rubyonrails.org/v3.2.14/</a>.
</p>
<p>
The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 8dcd544a52..cd4a1a0792 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -209,7 +209,7 @@ class PeopleController < ActionController::Base
# Request reply.
def update
person = current_account.people.find(params[:id])
- person.update_attributes!(person_params)
+ person.update!(person_params)
redirect_to person
end
@@ -808,11 +808,11 @@ class AdminsController < ApplicationController
private
- def authenticate
- authenticate_or_request_with_http_digest do |username|
- USERS[username]
+ def authenticate
+ authenticate_or_request_with_http_digest do |username|
+ USERS[username]
+ end
end
- end
end
```
@@ -839,13 +839,13 @@ class ClientsController < ApplicationController
private
- def generate_pdf(client)
- Prawn::Document.new do
- text client.name, align: :center
- text "Address: #{client.address}"
- text "Email: #{client.email}"
- end.render
- end
+ def generate_pdf(client)
+ Prawn::Document.new do
+ text client.name, align: :center
+ text "Address: #{client.address}"
+ text "Email: #{client.email}"
+ end.render
+ end
end
```
@@ -1048,9 +1048,9 @@ class ApplicationController < ActionController::Base
private
- def record_not_found
- render text: "404 Not Found", status: 404
- end
+ def record_not_found
+ render text: "404 Not Found", status: 404
+ end
end
```
@@ -1062,10 +1062,10 @@ class ApplicationController < ActionController::Base
private
- def user_not_authorized
- flash[:error] = "You don't have access to this section."
- redirect_to :back
- end
+ def user_not_authorized
+ flash[:error] = "You don't have access to this section."
+ redirect_to :back
+ end
end
class ClientsController < ApplicationController
@@ -1079,10 +1079,10 @@ class ClientsController < ApplicationController
private
- # If the user is not authorized, just throw the exception.
- def check_authorization
- raise User::NotAuthorized unless current_user.admin?
- end
+ # If the user is not authorized, just throw the exception.
+ def check_authorization
+ raise User::NotAuthorized unless current_user.admin?
+ end
end
```
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index bf34799eb3..61fd762304 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -105,7 +105,7 @@ will be the template used for the email, formatted in HTML:
<h1>Welcome to example.com, <%= @user.name %></h1>
<p>
You have successfully signed up to example.com,
- your username is: <%= @user.login %>.<br/>
+ your username is: <%= @user.login %>.<br>
</p>
<p>
To login to the site, just follow this link: <%= @url %>.
@@ -569,25 +569,25 @@ class UserMailer < ActionMailer::Base
private
- def set_delivery_options
- # You have access to the mail instance,
- # @business and @user instance variables here
- if @business && @business.has_smtp_settings?
- mail.delivery_method.settings.merge!(@business.smtp_settings)
+ def set_delivery_options
+ # You have access to the mail instance,
+ # @business and @user instance variables here
+ if @business && @business.has_smtp_settings?
+ mail.delivery_method.settings.merge!(@business.smtp_settings)
+ end
end
- end
- def prevent_delivery_to_guests
- if @user && @user.guest?
- mail.perform_deliveries = false
+ def prevent_delivery_to_guests
+ if @user && @user.guest?
+ mail.perform_deliveries = false
+ end
end
- end
- def set_business_headers
- if @business
- headers["X-SMTPAPI-CATEGORY"] = @business.code
+ def set_business_headers
+ if @business
+ headers["X-SMTPAPI-CATEGORY"] = @business.code
+ end
end
- end
end
```
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 5cda104138..d19dd11181 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -68,7 +68,7 @@ Consider the following loop for names:
```html+erb
<h1>Names of all the people</h1>
<% @people.each do |person| %>
- Name: <%= person.name %><br/>
+ Name: <%= person.name %><br>
<% end %>
```
@@ -269,7 +269,7 @@ Rails will render the `_product_ruler` partial (with no data passed to it) betwe
### Layouts
-Layouts can be used to render a common view template around the results of Rails controller actions. Typically, every Rails has a couple of overall layouts that most pages are rendered within. For example, a site might have a layout for a logged in user, and a layout for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us." You would expect each layout to have a different look and feel. You can read more details about Layouts in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
+Layouts can be used to render a common view template around the results of Rails controller actions. Typically, every Rails application has a couple of overall layouts that most pages are rendered within. For example, a site might have a layout for a logged in user, and a layout for the marketing or sales side of the site. The logged in user layout might include top-level navigation that should be present across many controller actions. The sales layout for a SaaS app might include top-level navigation for things like "Pricing" and "Contact Us." You would expect each layout to have a different look and feel. You can read more details about Layouts in the [Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
Partial Layouts
---------------
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 1d87646e49..0019d08328 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -120,8 +120,8 @@ class Person
end
def save
- @previously_changed = changes
# do save work...
+ changes_applied
end
end
```
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index a80c3b6c8e..a184f0753d 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -48,10 +48,10 @@ overall database access code.
Active Record gives us several mechanisms, the most important being the ability
to:
-* Represent models and their data
-* Represent associations between these models
-* Represent inheritance hierarchies through related models
-* Validate models before they get persisted to the database
+* Represent models and their data.
+* Represent associations between these models.
+* Represent inheritance hierarchies through related models.
+* Validate models before they get persisted to the database.
* Perform database operations in an object-oriented fashion.
Convention over Configuration in Active Record
@@ -78,15 +78,15 @@ of two or more words, the model class name should follow the Ruby conventions,
using the CamelCase form, while the table name must contain the words separated
by underscores. Examples:
-* Database Table - Plural with underscores separating words (e.g., `book_clubs`)
+* Database Table - Plural with underscores separating words (e.g., `book_clubs`).
* Model Class - Singular with the first letter of each word capitalized (e.g.,
-`BookClub`)
+`BookClub`).
| Model / Class | Table / Schema |
| ------------- | -------------- |
| `Post` | `posts` |
| `LineItem` | `line_items` |
-| `Deer` | `deer` |
+| `Deer` | `deers` |
| `Mouse` | `mice` |
| `Person` | `people` |
@@ -101,7 +101,7 @@ depending on the purpose of these columns.
fields that Active Record will look for when you create associations between
your models.
* **Primary keys** - By default, Active Record will use an integer column named
- `id` as the table's primary key. When using [Rails
+ `id` as the table's primary key. When using [Active Record
Migrations](migrations.html) to create your tables, this column will be
automatically created.
@@ -116,7 +116,7 @@ to Active Record instances:
locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to
a model.
* `type` - Specifies that the model uses [Single Table
- Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
+ Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Single+table+inheritance).
* `(association_name)_type` - Stores the type for
[polymorphic associations](association_basics.html#polymorphic-associations).
* `(table_name)_count` - Used to cache the number of belonging objects on
@@ -368,6 +368,6 @@ Rails keeps track of which files have been committed to the database and
provides rollback features. To actually create the table, you'd run `rake db:migrate`
and to roll it back, `rake db:rollback`.
-Note that the above code is database-agnostic: it will run in MySQL, postgresql,
-Oracle and others. You can learn more about migrations in the [Active Record
-Migrations guide](migrations.html)
+Note that the above code is database-agnostic: it will run in MySQL,
+PostgreSQL, Oracle and others. You can learn more about migrations in the
+[Active Record Migrations guide](migrations.html).
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 95eb84dd1f..5cc6ca5798 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -35,11 +35,11 @@ class User < ActiveRecord::Base
before_validation :ensure_login_has_a_value
protected
- def ensure_login_has_a_value
- if login.nil?
- self.login = email unless email.blank?
+ def ensure_login_has_a_value
+ if login.nil?
+ self.login = email unless email.blank?
+ end
end
- end
end
```
@@ -65,13 +65,13 @@ class User < ActiveRecord::Base
after_validation :set_location, on: [ :create, :update ]
protected
- def normalize_name
- self.name = self.name.downcase.titleize
- end
+ def normalize_name
+ self.name = self.name.downcase.titleize
+ end
- def set_location
- self.location = LocationService.query(self)
- end
+ def set_location
+ self.location = LocationService.query(self)
+ end
end
```
@@ -204,7 +204,7 @@ As you start registering new callbacks for your models, they will be queued for
The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception.
-WARNING. Raising an arbitrary exception may break code that expects `save` and its friends not to fail like that. The `ActiveRecord::Rollback` exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised.
+WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception.
Relational Callbacks
--------------------
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index ba0dc6d9eb..57e8e080f4 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -943,7 +943,7 @@ WARNING: This method only works with `INNER JOIN`.
Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clause for those associations when using the `joins` method.
-For example, consider the following `Category`, `Post`, `Comments` and `Guest` models:
+For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models:
```ruby
class Category < ActiveRecord::Base
@@ -1189,7 +1189,7 @@ class Post < ActiveRecord::Base
end
```
-This may then be called using this:
+Call the scope as if it were a class method:
```ruby
Post.created_before(Time.zone.now)
@@ -1358,7 +1358,7 @@ COMMIT
The new record might not be saved to the database; that depends on whether validations passed or not (just like `create`).
-Suppose we want to set the 'locked' attribute to true if we're
+Suppose we want to set the 'locked' attribute to `false` if we're
creating a new record, but we don't want to include it in the query. So
we want to find the client named "Andy", or if that client doesn't
exist, create a client named "Andy" which is not locked.
@@ -1466,7 +1466,7 @@ Client.pluck(:id, :name)
# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
```
-`pluck` makes it possible to replace code like
+`pluck` makes it possible to replace code like:
```ruby
Client.select(:id).map { |c| c.id }
@@ -1476,7 +1476,7 @@ Client.select(:id).map(&:id)
Client.select(:id, :name).map { |c| [c.id, c.name] }
```
-with
+with:
```ruby
Client.pluck(:id)
@@ -1484,6 +1484,37 @@ Client.pluck(:id)
Client.pluck(:id, :name)
```
+Unlike `select`, `pluck` directly converts a database result into a Ruby `Array`,
+without constructing `ActiveRecord` objects. This can mean better performance for
+a large or often-running query. However, any model method overrides will
+not be available. For example:
+
+```ruby
+class Client < ActiveRecord::Base
+ def name
+ "I am #{super}"
+ end
+end
+
+Client.select(:name).map &:name
+# => ["I am David", "I am Jeremy", "I am Jose"]
+
+Client.pluck(:name)
+# => ["David", "Jeremy", "Jose"]
+```
+
+Furthermore, unlike `select` and other `Relation` scopes, `pluck` triggers an immediate
+query, and thus cannot be chained with any further scopes, although it can work with
+scopes already constructed earlier:
+
+```ruby
+Client.pluck(:name).limit(1)
+# => NoMethodError: undefined method `limit' for #<Array:0x007ff34d3ad6d8>
+
+Client.limit(1).pluck(:name)
+# => ["David"]
+```
+
### `ids`
`ids` can be used to pluck all the IDs for the relation using the table's primary key.
@@ -1505,18 +1536,21 @@ Person.ids
Existence of Objects
--------------------
-If you simply want to check for the existence of the object there's a method called `exists?`. This method will query the database using the same query as `find`, but instead of returning an object or collection of objects it will return either `true` or `false`.
+If you simply want to check for the existence of the object there's a method called `exists?`.
+This method will query the database using the same query as `find`, but instead of returning an
+object or collection of objects it will return either `true` or `false`.
```ruby
Client.exists?(1)
```
-The `exists?` method also takes multiple ids, but the catch is that it will return true if any one of those records exists.
+The `exists?` method also takes multiple values, but the catch is that it will return `true` if any
+one of those records exists.
```ruby
-Client.exists?(1,2,3)
+Client.exists?(id: [1,2,3])
# or
-Client.exists?([1,2,3])
+Client.exists?(name: ['John', 'Sergei'])
```
It's even possible to use `exists?` without any arguments on a model or a relation.
@@ -1525,7 +1559,8 @@ It's even possible to use `exists?` without any arguments on a model or a relati
Client.where(first_name: 'Ryan').exists?
```
-The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` otherwise.
+The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false`
+otherwise.
```ruby
Client.exists?
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 0b2f0a47fa..0df52a655f 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -438,8 +438,6 @@ provide a personalized message or use `presence: true` instead. When
`:in` or `:within` have a lower limit of 1, you should either provide a
personalized message or call `presence` prior to `length`.
-The `size` helper is an alias for `length`.
-
### `numericality`
This helper validates that your attributes have only numeric values. By
@@ -528,7 +526,7 @@ If you validate the presence of an object associated via a `has_one` or
Since `false.blank?` is true, if you want to validate the presence of a boolean
field you should use `validates :field_name, inclusion: { in: [true, false] }`.
-The default error message is _"can't be empty"_.
+The default error message is _"can't be blank"_.
### `absence`
@@ -783,7 +781,7 @@ end
Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank
```
-There is also an ability to pass custom exception to `:strict` option
+There is also an ability to pass custom exception to `:strict` option.
```ruby
class Person < ActiveRecord::Base
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index ca023f7f66..0370e40012 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -96,12 +96,13 @@ INFO: The predicate for strings uses the Unicode-aware character class `[:space:
WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank.
-For example, this method from `ActionDispatch::Session::AbstractStore` uses `blank?` for checking whether a session key is present:
+For example, this method from `ActionController::HttpAuthentication::Token::ControllerMethods` uses `blank?` for checking whether a token is present:
```ruby
-def ensure_session_key!
- if @key.blank?
- raise ArgumentError, 'A key is required...'
+def authenticate(controller, &login_procedure)
+ token, options = token_and_options(controller.request)
+ unless token.blank?
+ login_procedure.call(token, options)
end
end
```
@@ -420,11 +421,9 @@ NOTE: Defined in `active_support/core_ext/object/with_options.rb`.
### JSON support
-Active Support provides a better implemention of `to_json` than the +json+ gem ordinarily provides for Ruby objects. This is because some classes, like +Hash+ and +OrderedHash+ needs special handling in order to provide a proper JSON representation.
+Active Support provides a better implementation of `to_json` than the +json+ gem ordinarily provides for Ruby objects. This is because some classes, like +Hash+, +OrderedHash+, and +Process::Status+ need special handling in order to provide a proper JSON representation.
-Active Support also provides an implementation of `as_json` for the <tt>Process::Status</tt> class.
-
-NOTE: Defined in `active_support/core_ext/object/to_json.rb`.
+NOTE: Defined in `active_support/core_ext/object/json.rb`.
### Instance Variables
@@ -1999,7 +1998,7 @@ Produce a string representation of a number in human-readable words:
1234567890123456.to_s(:human) # => "1.23 Quadrillion"
```
-NOTE: Defined in `active_support/core_ext/numeric/formatting.rb`.
+NOTE: Defined in `active_support/core_ext/numeric/conversions.rb`.
Extensions to `Integer`
-----------------------
@@ -2444,7 +2443,7 @@ dup[1][2] = 4
array[1][2] == nil # => true
```
-NOTE: Defined in `active_support/core_ext/array/deep_dup.rb`.
+NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`.
### Grouping
@@ -2670,45 +2669,7 @@ hash[:b][:e] == nil # => true
hash[:b][:d] == [3, 4] # => true
```
-NOTE: Defined in `active_support/core_ext/hash/deep_dup.rb`.
-
-### Diffing
-
-The method `diff` returns a hash that represents a diff of the receiver and the argument with the following logic:
-
-* Pairs `key`, `value` that exist in both hashes do not belong to the diff hash.
-
-* If both hashes have `key`, but with different values, the pair in the receiver wins.
-
-* The rest is just merged.
-
-```ruby
-{a: 1}.diff(a: 1)
-# => {}, first rule
-
-{a: 1}.diff(a: 2)
-# => {:a=>1}, second rule
-
-{a: 1}.diff(b: 2)
-# => {:a=>1, :b=>2}, third rule
-
-{a: 1, b: 2, c: 3}.diff(b: 1, c: 3, d: 4)
-# => {:a=>1, :b=>2, :d=>4}, all rules
-
-{}.diff({}) # => {}
-{a: 1}.diff({}) # => {:a=>1}
-{}.diff(a: 1) # => {:a=>1}
-```
-
-An important property of this diff hash is that you can retrieve the original hash by applying `diff` twice:
-
-```ruby
-hash.diff(hash2).diff(hash2) == hash
-```
-
-Diffing hashes may be useful for error messages related to expected option hashes for example.
-
-NOTE: Defined in `active_support/core_ext/hash/diff.rb`.
+NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`.
### Working with Keys
@@ -2736,14 +2697,14 @@ NOTE: Defined in `active_support/core_ext/hash/except.rb`.
The method `transform_keys` accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver:
```ruby
-{nil => nil, 1 => 1, a: :a}.transform_keys{ |key| key.to_s.upcase }
+{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase }
# => {"" => nil, "A" => :a, "1" => 1}
```
The result in case of collision is undefined:
```ruby
-{"a" => 1, a: 2}.transform_keys{ |key| key.to_s.upcase }
+{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase }
# => {"A" => 2}, in my test, can't rely on this result though
```
@@ -2751,11 +2712,11 @@ This method may be useful for example to build specialized conversions. For inst
```ruby
def stringify_keys
- transform_keys{ |key| key.to_s }
+ transform_keys { |key| key.to_s }
end
...
def symbolize_keys
- transform_keys{ |key| key.to_sym rescue key }
+ transform_keys { |key| key.to_sym rescue key }
end
```
@@ -2764,7 +2725,7 @@ There's also the bang variant `transform_keys!` that applies the block operation
Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to perform the block operation on all the keys in the given hash and all the hashes nested into it. An example of the result is:
```ruby
-{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys{ |key| key.to_s.upcase }
+{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys { |key| key.to_s.upcase }
# => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}}
```
@@ -3843,13 +3804,13 @@ def default_helper_module!
module_path = module_name.underscore
helper module_path
rescue MissingSourceFile => e
- raise e unless e.is_missing? "#{module_path}_helper"
+ raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
```
-NOTE: Defined in `active_support/core_ext/name_error.rb`.
+NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
Extensions to `LoadError`
-------------------------
@@ -3872,4 +3833,4 @@ rescue NameError => e
end
```
-NOTE: Defined in `active_support/core_ext/load_error.rb`.
+NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`.
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 72aff1e0dd..e9d3712a2a 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -405,11 +405,10 @@ JavaScript and stylesheet.
* `image-url("rails.png")` becomes `url(/assets/rails.png)`
* `image-path("rails.png")` becomes `"/assets/rails.png"`.
-The more generic form can also be used but the asset path and class must both be
-specified:
+The more generic form can also be used:
-* `asset-url("rails.png", image)` becomes `url(/assets/rails.png)`
-* `asset-path("rails.png", image)` becomes `"/assets/rails.png"`
+* `asset-url("rails.png")` becomes `url(/assets/rails.png)`
+* `asset-path("rails.png")` becomes `"/assets/rails.png"`
#### JavaScript/CoffeeScript and ERB
@@ -503,7 +502,11 @@ NOTE. If you want to use multiple Sass files, you should generally use the [Sass
rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) instead
of these Sprockets directives. Using Sprockets directives all Sass files exist
within their own scope, making variables or mixins only available within the
-document they were defined in.
+document they were defined in. You can do file globbing as well using
+`@import "*"`, and `@import "**/*"` to add the whole tree equivalent to how
+`require_tree` works. Check the [sass-rails
+documentation](https://github.com/rails/sass-rails#features) for more info and
+important caveats.
You can have as many manifest files as you need. For example, the `admin.css`
and `admin.js` manifest could contain the JS and CSS files that are used for the
@@ -1040,17 +1043,22 @@ Making Your Library or Gem a Pre-Processor
As Sprockets uses [Tilt](https://github.com/rtomayko/tilt) as a generic
interface to different templating engines, your gem should just implement the
Tilt template protocol. Normally, you would subclass `Tilt::Template` and
-reimplement `evaluate` method to return final output. Template source is stored
-at `@code`. Have a look at
+reimplement the `prepare` method, which initializes your template, and the
+`evaluate` method, which returns the processed source. The original source is
+stored in `data`. Have a look at
[`Tilt::Template`](https://github.com/rtomayko/tilt/blob/master/lib/tilt/template.rb)
sources to learn more.
```ruby
module BangBang
class Template < ::Tilt::Template
+ def prepare
+ # Do any initialization here
+ end
+
# Adds a "!" to original template.
def evaluate(scope, locals, &block)
- "#{@code}!"
+ "#{data}!"
end
end
end
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index c58dd2e90a..c0482f6106 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -261,7 +261,10 @@ With `through: :sections` specified, Rails will now understand:
### The `has_one :through` Association
-A `has_one :through` association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding _through_ a third model. For example, if each supplier has one account, and each account is associated with one account history, then the customer model could look like this:
+A `has_one :through` association sets up a one-to-one connection with another model. This association indicates
+that the declaring model can be matched with one instance of another model by proceeding _through_ a third model.
+For example, if each supplier has one account, and each account is associated with one account history, then the
+supplier model could look like this:
```ruby
class Supplier < ActiveRecord::Base
@@ -337,7 +340,7 @@ class CreateAssembliesAndParts < ActiveRecord::Migration
t.timestamps
end
- create_table :assemblies_parts do |t|
+ create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly
t.belongs_to :part
end
@@ -715,7 +718,7 @@ The `belongs_to` association creates a one-to-one match with another model. In d
#### Methods Added by `belongs_to`
-When you declare a `belongs_to` association, the declaring class automatically gains four methods related to the association:
+When you declare a `belongs_to` association, the declaring class automatically gains five methods related to the association:
* `association(force_reload = false)`
* `association=(associate)`
@@ -1019,7 +1022,7 @@ The `has_one` association creates a one-to-one match with another model. In data
#### Methods Added by `has_one`
-When you declare a `has_one` association, the declaring class automatically gains four methods related to the association:
+When you declare a `has_one` association, the declaring class automatically gains five methods related to the association:
* `association(force_reload = false)`
* `association=(associate)`
@@ -1137,10 +1140,10 @@ Controls what happens to the associated object when its owner is destroyed:
* `:restrict_with_exception` causes an exception to be raised if there is an associated record
* `:restrict_with_error` causes an error to be added to the owner if there is an associated object
-It's necessary not to set or leave `:nullify` option for those associations
-that have `NOT NULL` database constraints. If you don't set `dependent` to
-destroy such associations you won't be able to change the associated object
-because initial associated object foreign key will be set to unallowed `NULL`
+It's necessary not to set or leave `:nullify` option for those associations
+that have `NOT NULL` database constraints. If you don't set `dependent` to
+destroy such associations you won't be able to change the associated object
+because initial associated object foreign key will be set to unallowed `NULL`
value.
##### `:foreign_key`
@@ -1286,7 +1289,7 @@ The `has_many` association creates a one-to-many relationship with another model
#### Methods Added by `has_many`
-When you declare a `has_many` association, the declaring class automatically gains 13 methods related to the association:
+When you declare a `has_many` association, the declaring class automatically gains 16 methods related to the association:
* `collection(force_reload = false)`
* `collection<<(object, ...)`
@@ -1775,7 +1778,7 @@ The `has_and_belongs_to_many` association creates a many-to-many relationship wi
#### Methods Added by `has_and_belongs_to_many`
-When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 13 methods related to the association:
+When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 16 methods related to the association:
* `collection(force_reload = false)`
* `collection<<(object, ...)`
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 3cf6631c32..b0ab88bf59 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -225,7 +225,7 @@ This is the default cache store implementation.
### ActiveSupport::Cache::MemCacheStore
-This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy.
+This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very high performance and redundancy.
When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites.
@@ -327,7 +327,7 @@ class ProductsController < ApplicationController
end
```
-Instead of a options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
+Instead of an options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
```ruby
class ProductsController < ApplicationController
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index f11e08a0c1..1b0b93c3bc 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -1,8 +1,6 @@
The Rails Command Line
======================
-Rails comes with every command line tool you'll need to
-
After reading this guide, you will know:
* How to create a Rails application.
@@ -381,13 +379,14 @@ About your application's environment
Ruby version 1.9.3 (x86_64-linux)
RubyGems version 1.3.6
Rack version 1.3
-Rails version 4.0.0
+Rails version 4.1.0
JavaScript Runtime Node.js (V8)
-Active Record version 4.0.0
-Action Pack version 4.0.0
-Action Mailer version 4.0.0
-Active Support version 4.0.0
-Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::EncryptedCookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
+Active Record version 4.1.0
+Action Pack version 4.1.0
+Action View version 4.1.0
+Action Mailer version 4.1.0
+Active Support version 4.1.0
+Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag
Application root /home/foobar/commandsapp
Environment development
Database adapter sqlite3
@@ -493,7 +492,9 @@ The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp`
### Custom Rake Tasks
-Custom rake tasks have a `.rake` extension and are placed in `Rails.root/lib/tasks`.
+Custom rake tasks have a `.rake` extension and are placed in
+`Rails.root/lib/tasks`. You can create these custom rake tasks with the
+`bin/rails generate task` command.
```ruby
desc "I am short, but comprehensive description for my cool task"
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 3cf5cdd71f..8ac34c9716 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -103,7 +103,7 @@ numbers. New applications filter out passwords by adding the following `config.f
* `config.force_ssl` forces all requests to be under HTTPS protocol by using `ActionDispatch::SSL` middleware.
-* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to a instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
+* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes except production, where it defaults to `Logger::Formatter`.
* `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`.
@@ -261,6 +261,8 @@ config.middleware.delete "Rack::MethodOverride"
* `config.active_record.table_name_suffix` lets you set a global string to be appended to table names. If you set this to `_northwest`, then the Customer class will look for `customers_northwest` as its table. The default is an empty string.
+* `config.active_record.schema_migrations_table_name` lets you set a string to be used as the name of the schema migrations table.
+
* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to true (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table.
* `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc` for Rails, although Active Record defaults to `:local` when used outside of Rails.
@@ -273,6 +275,12 @@ config.middleware.delete "Rack::MethodOverride"
* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:number`.
+* `config.active_record.record_timestamps` is a boolean value which controls whether or not timestamping of `create` and `update` operations on a model occur. The default value is `true`.
+
+* `config.active_record.partial_writes` is a boolean value and controls whether or not partial writes are used (i.e. whether updates only set attributes that are dirty). Note that when using partial writes, you should also use optimistic locking `config.active_record.lock_optimistically` since concurrent updates may write attributes based on a possibly stale read state. The default value is `true`.
+
+* `config.active_record.attribute_types_cached_by_default` sets the attribute types that `ActiveRecord::AttributeMethods` will cache by default on reads. The default is `[:datetime, :timestamp, :time, :date]`.
+
The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
@@ -303,7 +311,7 @@ The schema dumper adds one additional configuration option:
* `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`.
-* `config.action_controller.action_on_unpermitted_params` enables logging or raising an exception if parameters that are not explicitly permitted are found. Set to `:log` or `:raise` to enable. The default value is `:log` in development and test environments, and `false` in all other environments.
+* `config.action_controller.action_on_unpermitted_parameters` enables logging or raising an exception if parameters that are not explicitly permitted are found. Set to `:log` or `:raise` to enable. The default value is `:log` in development and test environments, and `false` in all other environments.
### Configuring Action Dispatch
@@ -528,7 +536,7 @@ Change the username and password in the `development` section as appropriate.
By default Rails ships with three environments: "development", "test", and "production". While these are sufficient for most use cases, there are circumstances when you want more environments.
-Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server just by create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there.
+Imagine you have a server which mirrors the production environment but is only used for testing. Such a server is commonly called a "staging server". To define an environment called "staging" for this server, just create a file called `config/environments/staging.rb`. Please use the contents of any existing file in `config/environments` as a starting point and make the necessary changes from there.
That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console staging`, `Rails.env.staging?` works, etc.
@@ -604,7 +612,7 @@ Rails has 5 initialization events which can be hooked into (listed in the order
* `before_eager_load`: This is run directly before eager loading occurs, which is the default behavior for the `production` environment and not for the `development` environment.
-* `after_initialize`: Run directly after the initialization of the application, but before the application initializers are run.
+* `after_initialize`: Run directly after the initialization of the application, after the application initializers in `config/initializers` are run.
To define an event for these hooks, use the block syntax within a `Rails::Application`, `Rails::Railtie` or `Rails::Engine` subclass:
@@ -759,4 +767,15 @@ Since the connection pooling is handled inside of Active Record by default, all
Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue.
+If you try to use more connections than are available, Active Record will block
+and wait for a connection from the pool. When it cannot get connection, a timeout
+error similar to given below will be thrown.
+
+```ruby
+ActiveRecord::ConnectionTimeoutError - could not obtain a database connection within 5 seconds. The max pool size is currently 5; consider increasing it:
+```
+
+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 have enabled `Rails.threadsafe!` mode then 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.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index fc6b2f992a..a6956eb009 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -219,7 +219,7 @@ When working with documentation, please take into account the [API Documentation
NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements.
-NOTE: To help our CI servers you can add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes.
+NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes.
WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails.
@@ -430,13 +430,18 @@ $ git push origin branch_name
### Issue a Pull Request
-Navigate to the Rails repository you just pushed to (e.g. https://github.com/your-user-name/rails) and press "Pull Request" in the upper right hand corner.
+Navigate to the Rails repository you just pushed to (e.g.
+https://github.com/your-user-name/rails) and click on "Pull Requests" seen in
+the right panel. On the next page, press "New pull request" in the upper right
+hand corner.
-Write your branch name in the branch field (this is filled with "master" by default) and press "Update Commit Range".
+Click on "Edit", if you need to change the branches being compared (it compares
+"master" by default) and press "Click to create a pull request for this
+comparison".
-Ensure the changesets you introduced are included in the "Commits" tab. Ensure that the "Files Changed" incorporate all of your changes.
-
-Fill in some details about your potential patch including a meaningful title. When finished, press "Send pull request". The Rails core team will be notified about your submission.
+Ensure the changesets you introduced are included. Fill in some details about
+your potential patch including a meaningful title. When finished, press "Send
+pull request". The Rails core team will be notified about your submission.
### Get some Feedback
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 14d65e4747..226137c89a 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -233,7 +233,7 @@ only evaluated if the output level is the same or included in the allowed level
(i.e. lazy loading). The same code rewritten would be:
```ruby
-logger.debug {"Person attibutes hash: #{@person.attributes.inspect}"}
+logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}
```
The contents of the block, and therefore the string interpolation, is only
@@ -386,7 +386,7 @@ Finally, to see where you are in the code again you can type `list=`
7
8 respond_to do |format|
9 format.html # index.html.erb
- 10 format.json { render :json => @posts }
+ 10 format.json { render json: @posts }
```
### The Context
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 1b16f4e516..1bf9ff95e1 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -150,6 +150,13 @@
url: ruby_on_rails_guides_guidelines.html
description: This guide documents the Ruby on Rails guides guidelines.
-
+ name: Maintenance Policy
+ documents:
+ -
+ name: Maintenance Policy
+ url: maintenance_policy.html
+ description: What versions of Ruby on Rails are currently supported, and when to expect new versions.
+-
name: Release Notes
documents:
-
diff --git a/guides/source/engines.md b/guides/source/engines.md
index bc404ccb7f..c71b728ef7 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -307,7 +307,11 @@ create test/models/blorgh/comment_test.rb
create test/fixtures/blorgh/comments.yml
```
-This generator call will generate just the necessary model files it needs, namespacing the files under a `blorgh` directory and creating a model class called `Blorgh::Comment`.
+This generator call will generate just the necessary model files it needs, namespacing the files under a `blorgh` directory and creating a model class called `Blorgh::Comment`. Now run the migration to create our blorgh_comments table:
+
+```bash
+$ rake db:migrate
+```
To show the comments on a post, edit `app/views/blorgh/posts/show.html.erb` and add this line before the "Edit" link:
@@ -399,9 +403,9 @@ def create
end
private
-def comment_params
- params.require(:comment).permit(:text)
-end
+ def comment_params
+ params.require(:comment).permit(:text)
+ end
```
This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error:
@@ -850,7 +854,6 @@ module Blorgh::Concerns::Models::Post
before_save :set_author
private
-
def set_author
self.author = User.find_or_create_by(name: author_name)
end
@@ -951,7 +954,7 @@ INFO. Remember that in order to use languages like Sass or CoffeeScript, you sho
There are some situations where your engine's assets are not required by the host application. For example, say that you've created
an admin functionality that only exists for your engine. In this case, the host application doesn't need to require `admin.css`
-or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorg/admin.css"` in it's stylesheets. In this situation, you should explicitly define these assets for precompilation.
+or `admin.js`. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include `"blorgh/admin.css"` in it's stylesheets. In this situation, you should explicitly define these assets for precompilation.
This tells sprockets to add your engine assets when `rake assets:precompile` is ran.
You can define assets for precompilation in `engine.rb`
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 578cfbe105..4b6d8a93f0 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -67,7 +67,7 @@ To create this form you will use `form_tag`, `label_tag`, `text_field_tag`, and
This will generate the following HTML:
```html
-<form accept-charset="UTF-8" action="/search" method="get">
+<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
<label for="q">Search for:</label>
<input id="q" name="q" type="text" />
<input name="commit" type="submit" value="Search" />
@@ -914,9 +914,9 @@ def create
end
private
-def person_params
- params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street])
-end
+ def person_params
+ params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street])
+ end
```
### Removing Objects
@@ -973,4 +973,4 @@ As a convenience you can instead pass the symbol `:all_blank` which will create
### Adding Fields on the Fly
-Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new child' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current javascript date (milliseconds after the epoch) is a common choice.
+Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice.
diff --git a/guides/source/generators.md b/guides/source/generators.md
index a8a34d0ac4..e9c8ef0225 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -35,7 +35,7 @@ $ rails generate helper --help
Creating Your First Generator
-----------------------------
-Since Rails 3.0, generators are built on top of [Thor](https://github.com/wycats/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 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:
@@ -47,7 +47,7 @@ class InitializerGenerator < Rails::Generators::Base
end
```
-NOTE: `create_file` is a method provided by `Thor::Actions`. Documentation for `create_file` and other Thor methods can be found in [Thor's documentation](http://rdoc.info/github/wycats/thor/master/Thor/Actions.html)
+NOTE: `create_file` is a method provided by `Thor::Actions`. Documentation for `create_file` and other Thor methods can be found in [Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html)
Our new generator is quite simple: it inherits from `Rails::Generators::Base` and has one method definition. When a generator is invoked, each public method in the generator is executed sequentially in the order that it is defined. Finally, we invoke the `create_file` method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API.
@@ -171,7 +171,7 @@ Before we customize our workflow, let's first see what our scaffold looks like:
```bash
$ rails generate scaffold User name:string
invoke active_record
- create db/migrate/20091120125558_create_users.rb
+ create db/migrate/20130924151154_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
@@ -193,6 +193,9 @@ $ rails generate scaffold User name:string
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
invoke assets
invoke coffee
create app/assets/javascripts/users.js.coffee
@@ -204,7 +207,7 @@ $ rails generate scaffold User name:string
Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
-Our first customization on the workflow will be to stop generating stylesheets and test fixtures for scaffolds. We can achieve that by changing our configuration to the following:
+Our first customization on the workflow will be to stop generating stylesheets, javascripts and test fixtures for scaffolds. We can achieve that by changing our configuration to the following:
```ruby
config.generators do |g|
@@ -212,20 +215,28 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
+ g.javascripts false
end
```
-If we generate another resource with the scaffold generator, we can see that neither stylesheets nor fixtures are created anymore. If you want to customize it further, for example to use DataMapper and RSpec instead of Active Record and TestUnit, it's just a matter of adding their gems to your application and configuring your generators.
+If we generate another resource with the scaffold generator, we can see that stylesheets, javascripts and fixtures are not created anymore. If you want to customize it further, for example to use DataMapper and RSpec instead of Active Record and TestUnit, it's just a matter of adding their gems to your application and configuring your generators.
To demonstrate this, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator within the rails namespace, as this is where rails searches for generators used as hooks:
```bash
$ rails generate generator rails/my_helper
+ create lib/generators/rails/my_helper
+ create lib/generators/rails/my_helper/my_helper_generator.rb
+ create lib/generators/rails/my_helper/USAGE
+ create lib/generators/rails/my_helper/templates
```
-After that, we can delete both the `templates` directory and the `source_root` class method from our new generators, because we are not going to need them. So our new generator looks like the following:
+After that, we can delete both the `templates` directory and the `source_root`
+class method call from our new generator, because we are not going to need them.
+Add the method below, so our generator looks like the following:
```ruby
+# lib/generators/rails/my_helper/my_helper_generator.rb
class Rails::MyHelperGenerator < Rails::Generators::NamedBase
def create_helper_file
create_file "app/helpers/#{file_name}_helper.rb", <<-FILE
@@ -241,6 +252,7 @@ We can try out our new generator by creating a helper for users:
```bash
$ rails generate my_helper products
+ create app/helpers/products_helper.rb
```
And it will generate the following helper file in `app/helpers`:
@@ -259,6 +271,7 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
+ g.javascripts false
g.helper :my_helper
end
```
@@ -279,6 +292,7 @@ Since Rails 3.0, this is easy to do due to the hooks concept. Our new helper doe
To do that, we can change the generator this way:
```ruby
+# lib/generators/rails/my_helper/my_helper_generator.rb
class Rails::MyHelperGenerator < Rails::Generators::NamedBase
def create_helper_file
create_file "app/helpers/#{file_name}_helper.rb", <<-FILE
@@ -322,6 +336,7 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :test_unit, fixture: false
g.stylesheets false
+ g.javascripts false
end
```
@@ -340,6 +355,7 @@ config.generators do |g|
g.template_engine :erb
g.test_framework :shoulda, fixture: false
g.stylesheets false
+ g.javascripts false
# Add a fallback!
g.fallbacks[:shoulda] = :test_unit
@@ -351,7 +367,7 @@ Now, if you create a Comment scaffold, you will see that the shoulda generators
```bash
$ rails generate scaffold Comment body:text
invoke active_record
- create db/migrate/20091120151323_create_comments.rb
+ create db/migrate/20130924143118_create_comments.rb
create app/models/comment.rb
invoke shoulda
create test/models/comment_test.rb
@@ -373,6 +389,9 @@ $ rails generate scaffold Comment body:text
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
invoke assets
invoke coffee
create app/assets/javascripts/comments.js.coffee
@@ -422,7 +441,7 @@ Generator methods
The following are methods available for both generators and templates for Rails.
-NOTE: Methods provided by Thor are not covered this guide and can be found in [Thor's documentation](http://rdoc.info/github/wycats/thor/master/Thor/Actions.html)
+NOTE: Methods provided by Thor are not covered this guide and can be found in [Thor's documentation](http://rdoc.info/github/erikhuda/thor/master/Thor/Actions.html)
### `gem`
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 81e57aee34..2f322d15da 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -22,8 +22,8 @@ with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or newer
-* The [RubyGems](http://rubygems.org/) packaging system
- * To learn more about RubyGems, please read the [RubyGems User Guide](http://docs.rubygems.org/read/book/1)
+* The [RubyGems](http://rubygems.org) packaging system
+ * To learn more about RubyGems, please read the [RubyGems Guides](http://guides.rubygems.org)
* A working installation of the [SQLite3 Database](http://www.sqlite.org)
Rails is a web application framework running on the Ruby programming language.
@@ -54,9 +54,11 @@ learned elsewhere, you may have a less happy experience.
The Rails philosophy includes two major guiding principles:
-* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and over again is a bad thing.
-* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to
-do it, rather than requiring you to specify every little thing through endless configuration files.
+* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and
+ over again is a bad thing.
+* Convention Over Configuration - means that Rails makes assumptions about what
+ you want to do and how you're going to do it, rather than requiring you to
+ specify every little thing through endless configuration files.
Creating a New Rails Project
----------------------------
@@ -94,10 +96,11 @@ $ gem install rails
```
TIP. A number of tools exist to help you quickly install Ruby and Ruby
-on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), while Mac OS X users can use
-[Rails One Click](http://railsoneclick.com).
+on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),
+while Mac OS X users can use [Rails One Click](http://railsoneclick.com).
-To verify that you have everything installed correctly, you should be able to run the following:
+To verify that you have everything installed correctly, you should be able to
+run the following:
```bash
$ rails --version
@@ -107,29 +110,38 @@ If it says something like "Rails 4.0.0", you are ready to continue.
### Creating the Blog Application
-Rails comes with a number of scripts called generators that are designed to make your development life easier by creating everything that's necessary to start working on a particular task. One of these is the new application generator, which will provide you with the foundation of a fresh Rails application so that you don't have to write it yourself.
+Rails comes with a number of scripts called generators that are designed to make
+your development life easier by creating everything that's necessary to start
+working on a particular task. One of these is the new application generator,
+which will provide you with the foundation of a fresh Rails application so that
+you don't have to write it yourself.
-To use this generator, open a terminal, navigate to a directory where you have rights to create files, and type:
+To use this generator, open a terminal, navigate to a directory where you have
+rights to create files, and type:
```bash
$ rails new blog
```
-This will create a Rails application called Blog in a directory called blog and install the gem dependencies that are already mentioned in `Gemfile` using `bundle install`.
+This will create a Rails application called Blog in a directory called blog and
+install the gem dependencies that are already mentioned in `Gemfile` using
+`bundle install`.
-TIP: You can see all of the command line options that the Rails
-application builder accepts by running `rails new -h`.
+TIP: You can see all of the command line options that the Rails application
+builder accepts by running `rails new -h`.
-After you create the blog application, switch to its folder to continue work directly in that application:
+After you create the blog application, switch to its folder to continue work
+directly in that application:
```bash
$ cd blog
```
-The `rails new blog` command we ran above created a folder in your
-working directory called `blog`. The `blog` directory has a number of
-auto-generated files and folders that make up the structure of a Rails
-application. Most of the work in this tutorial will happen in the `app/` folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default:
+The `rails new blog` command we ran above created a folder in your working
+directory called `blog`. The `blog` directory has a number of auto-generated
+files and folders that make up the structure of a Rails application. Most of the
+work in this tutorial will happen in the `app/` folder, but here's a basic
+rundown on the function of each of the files and folders that Rails created by default:
| File/Folder | Purpose |
| ----------- | ------- |
@@ -151,35 +163,65 @@ application. Most of the work in this tutorial will happen in the `app/` folder,
Hello, Rails!
-------------
-To begin with, let's get some text up on screen quickly. To do this, you need to get your Rails application server running.
+To begin with, let's get some text up on screen quickly. To do this, you need to
+get your Rails application server running.
### Starting up the Web Server
-You actually have a functional Rails application already. To see it, you need to start a web server on your development machine. You can do this by running the following in the root directory of your rails application:
+You actually have a functional Rails application already. To see it, you need to
+start a web server on your development machine. You can do this by running the
+following in the root directory of your rails application:
```bash
$ 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 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 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 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 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 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 webserver built into Ruby by default. To see your application in action, open a browser window and navigate to <http://localhost:3000>. You should see the Rails default information page:
+This will fire up WEBrick, a webserver built into Ruby by default. To see your
+application in action, open a browser window and navigate to <http://localhost:3000>.
+You should see the Rails default information page:
![Welcome Aboard screenshot](images/getting_started/rails_welcome.png)
-TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt cursor again. For most UNIX-like systems including Mac OS X this will be a dollar sign `$`. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server.
+TIP: To stop the web server, hit Ctrl+C in the terminal window where it's
+running. To verify the server has stopped you should see your command prompt
+cursor again. For most UNIX-like systems including Mac OS X this will be a
+dollar sign `$`. In development mode, Rails does not generally require you to
+restart the server; changes you make in files will be automatically picked up by
+the server.
-The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application's environment_ link to see a summary of your application's environment.
+The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it
+makes sure that you have your software configured correctly enough to serve a
+page. You can also click on the _About your application's environment_ link to
+see a summary of your application's environment.
### Say "Hello", Rails
-To get Rails saying "Hello", you need to create at minimum a _controller_ and a _view_.
+To get Rails saying "Hello", you need to create at minimum a _controller_ and a
+_view_.
-A controller's purpose is to receive specific requests for the application. _Routing_ decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view.
+A controller's purpose is to receive specific requests for the application.
+_Routing_ decides which controller receives which requests. Often, there is more
+than one route to each controller, and different routes can be served by
+different _actions_. Each action's purpose is to collect information to provide
+it to a view.
-A view's purpose is to display this information in a human readable format. An important distinction to make is that it is the _controller_, not the view, where information is collected. The view should just display that information. By default, view templates are written in a language called ERB (Embedded Ruby) which is converted by the request cycle in Rails before being sent to the user.
+A view's purpose is to display this information in a human readable format. An
+important distinction to make is that it is the _controller_, not the view,
+where information is collected. The view should just display that information.
+By default, view templates are written in a language called ERB (Embedded Ruby)
+which is converted by the request cycle in Rails before being sent to the user.
-To create a new controller, you will need to run the "controller" generator and tell it you want a controller called "welcome" with an action called "index", just like this:
+To create a new controller, you will need to run the "controller" generator and
+tell it you want a controller called "welcome" with an action called "index",
+just like this:
```bash
$ rails generate controller welcome index
@@ -206,9 +248,12 @@ invoke scss
create app/assets/stylesheets/welcome.css.scss
```
-Most important of these are of course the controller, located at `app/controllers/welcome_controller.rb` and the view, located at `app/views/welcome/index.html.erb`.
+Most important of these are of course the controller, located at `app/controllers/welcome_controller.rb`
+and the view, located at `app/views/welcome/index.html.erb`.
-Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all of the existing code in the file, and replace it with the following single line of code:
+Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all
+of the existing code in the file, and replace it with the following single line
+of code:
```html
<h1>Hello, Rails!</h1>
@@ -216,7 +261,10 @@ Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all
### Setting the Application Home Page
-Now that we have made the controller and view, we need to tell Rails when we want Hello Rails! to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, "Welcome Aboard" is occupying that spot.
+Now that we have made the controller and view, we need to tell Rails when we
+want `Hello, Rails!` to show up. In our case, we want it to show up when we
+navigate to the root URL of our site, <http://localhost:3000>. At the moment,
+"Welcome Aboard" is occupying that spot.
Next, you have to tell Rails where your actual home page is located.
@@ -233,27 +281,44 @@ Blog::Application.routes.draw do
# root "welcome#index"
```
-This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with `root` and uncomment it. It should look something like the following:
+This is your application's _routing file_ which holds entries in a special DSL
+(domain-specific language) that tells Rails how to connect incoming requests to
+controllers and actions. This file contains many sample routes on commented
+lines, and one of them actually shows you how to connect the root of your site
+to a specific controller and action. Find the line beginning with `root` and
+uncomment it. It should look something like the following:
```ruby
root "welcome#index"
```
-The `root "welcome#index"` tells Rails to map requests to the root of the application to the welcome controller's index action and `get "welcome/index"` tells Rails to map requests to <http://localhost:3000/welcome/index> to the welcome controller's index action. This was created earlier when you ran the controller generator (`rails generate controller welcome index`).
+The `root "welcome#index"` tells Rails to map requests to the root of the
+application to the welcome controller's index action and `get "welcome/index"`
+tells Rails to map requests to <http://localhost:3000/welcome/index> to the
+welcome controller's index action. This was created earlier when you ran the
+controller generator (`rails generate controller welcome index`).
-If you navigate to <http://localhost:3000> in your browser, you'll see the `Hello, Rails!` message you put into `app/views/welcome/index.html.erb`, indicating that this new route is indeed going to `WelcomeController`'s `index` action and is rendering the view correctly.
+If you navigate to <http://localhost:3000> in your browser, you'll see the
+`Hello, Rails!` message you put into `app/views/welcome/index.html.erb`,
+indicating that this new route is indeed going to `WelcomeController`'s `index`
+action and is rendering the view correctly.
TIP: For more information about routing, refer to [Rails Routing from the Outside In](routing.html).
Getting Up and Running
----------------------
-Now that you've seen how to create a controller, an action and a view, let's create something with a bit more substance.
+Now that you've seen how to create a controller, an action and a view, let's
+create something with a bit more substance.
-In the Blog application, you will now create a new _resource_. A resource is the term used for a collection of similar objects, such as posts, people or animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations.
+In the Blog application, you will now create a new _resource_. A resource is the
+term used for a collection of similar objects, such as posts, people or animals.
+You can create, read, update and destroy items for a resource and these
+operations are referred to as _CRUD_ operations.
-Rails provides a `resources` method which can be used to declare a standard REST resource.
-Here's what `config/routes.rb` should look like after the _post resource_ is declared.
+Rails provides a `resources` method which can be used to declare a standard REST
+resource. Here's what `config/routes.rb` should look like after the _post resource_
+is declared.
```ruby
Blog::Application.routes.draw do
@@ -283,32 +348,45 @@ edit_post GET /posts/:id/edit(.:format) posts#edit
root / welcome#index
```
-In the next section, you will add the ability to create new posts in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this:
+In the next section, you will add the ability to create new posts in your
+application and be able to view them. This is the "C" and the "R" from CRUD:
+creation and reading. The form for doing this will look like this:
![The new post form](images/getting_started/new_post.png)
-It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards.
+It will look a little basic for now, but that's ok. We'll look at improving the
+styling for it afterwards.
### Laying down the ground work
-The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see a routing error:
+The first thing that you are going to need to create a new post within the
+application is a place to do that. A great place for that would be at `/posts/new`.
+With the route already defined, requests can now be made to `/posts/new` in the
+application. Navigate to <http://localhost:3000/posts/new> and you'll see a
+routing error:
![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png)
-This error occurs because the route needs to have a controller defined in order to serve the request. The solution to this particular problem is simple: create a controller called `PostsController`. You can do this by running this command:
+This error occurs because the route needs to have a controller defined in order
+to serve the request. The solution to this particular problem is simple: create
+a controller called `PostsController`. You can do this by running this command:
```bash
$ rails g controller posts
```
-If you open up the newly generated `app/controllers/posts_controller.rb` you'll see a fairly empty controller:
+If you open up the newly generated `app/controllers/posts_controller.rb` you'll
+see a fairly empty controller:
```ruby
class PostsController < ApplicationController
end
```
-A controller is simply a class that is defined to inherit from `ApplicationController`. It's inside this class that you'll define methods that will become the actions for this controller. These actions will perform CRUD operations on the posts within our system.
+A controller is simply a class that is defined to inherit from `ApplicationController`.
+It's inside this class that you'll define methods that will become the actions
+for this controller. These actions will perform CRUD operations on the posts
+within our system.
NOTE: There are `public`, `private` and `protected` methods in `Ruby`
(for more details you can check on [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)).
@@ -318,44 +396,77 @@ If you refresh <http://localhost:3000/posts/new> now, you'll get a new error:
![Unknown action new for PostsController!](images/getting_started/unknown_action_new_for_posts.png)
-This error indicates that Rails cannot find the `new` action inside the `PostsController` that you just generated. This is because when controllers are generated in Rails they are empty by default, unless you tell it you wanted actions during the generation process.
+This error indicates that Rails cannot find the `new` action inside the `PostsController`
+that you just generated. This is because when controllers are generated in Rails
+they are empty by default, unless you tell it you wanted actions during the
+generation process.
-To manually define an action inside a controller, all you need to do is to define a new method inside the controller. Open `app/controllers/posts_controller.rb` and inside the `PostsController` class, define a `new` method like this:
+To manually define an action inside a controller, all you need to do is to
+define a new method inside the controller. Open `app/controllers/posts_controller.rb`
+and inside the `PostsController` class, define a `new` method like this:
```ruby
def new
end
```
-With the `new` method defined in `PostsController`, if you refresh <http://localhost:3000/posts/new> you'll see another error:
+With the `new` method defined in `PostsController`, if you refresh <http://localhost:3000/posts/new>
+you'll see another error:
![Template is missing for posts/new](images/getting_started/template_is_missing_posts_new.png)
-You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view available, Rails errors out.
+You're getting this error now because Rails expects plain actions like this one
+to have views associated with them to display their information. With no view
+available, Rails errors out.
-In the above image, the bottom line has been truncated. Let's see what the full thing looks like:
+In the above image, the bottom line has been truncated. Let's see what the full
+thing looks like:
<blockquote>
Missing template posts/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
</blockquote>
-That's quite a lot of text! Let's quickly go through and understand what each part of it does.
-
-The first part identifies what template is missing. In this case, it's the `posts/new` template. Rails will first look for this template. If not found, then it will attempt to load a template called `application/new`. It looks for one here because the `PostsController` inherits from `ApplicationController`.
-
-The next part of the message contains a hash. The `:locale` key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English - or "en" - template. The next key, `:formats` specifies the format of template to be served in response. The default format is `:html`, and so Rails is looking for an HTML template. The final key, `:handlers`, is telling us what _template handlers_ could be used to render our template. `:erb` is most commonly used for HTML templates, `:builder` is used for XML templates, and `:coffee` uses CoffeeScript to build JavaScript templates.
-
-The final part of this message tells us where Rails has looked for the templates. Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths.
-
-The simplest template that would work in this case would be one located at `app/views/posts/new.html.erb`. The extension of this file name is key: the first extension is the _format_ of the template, and the second extension is the _handler_ that will be used. Rails is attempting to find a template called `posts/new` within `app/views` for the application. The format for this template can only be `html` and the handler must be one of `erb`, `builder` or `coffee`. Because you want to create a new HTML form, you will be using the `ERB` language. Therefore the file should be called `posts/new.html.erb` and needs to be located inside the `app/views` directory of the application.
-
-Go ahead now and create a new file at `app/views/posts/new.html.erb` and write this content in it:
+That's quite a lot of text! Let's quickly go through and understand what each
+part of it does.
+
+The first part identifies what template is missing. In this case, it's the
+`posts/new` template. Rails will first look for this template. If not found,
+then it will attempt to load a template called `application/new`. It looks for
+one here because the `PostsController` inherits from `ApplicationController`.
+
+The next part of the message contains a hash. The `:locale` key in this hash
+simply indicates what spoken language template should be retrieved. By default,
+this is the English - or "en" - template. The next key, `:formats` specifies the
+format of template to be served in response. The default format is `:html`, and
+so Rails is looking for an HTML template. The final key, `:handlers`, is telling
+us what _template handlers_ could be used to render our template. `:erb` is most
+commonly used for HTML templates, `:builder` is used for XML templates, and
+`:coffee` uses CoffeeScript to build JavaScript templates.
+
+The final part of this message tells us where Rails has looked for the templates.
+Templates within a basic Rails application like this are kept in a single
+location, but in more complex applications it could be many different paths.
+
+The simplest template that would work in this case would be one located at
+`app/views/posts/new.html.erb`. The extension of this file name is key: the
+first extension is the _format_ of the template, and the second extension is the
+_handler_ that will be used. Rails is attempting to find a template called
+`posts/new` within `app/views` for the application. The format for this template
+can only be `html` and the handler must be one of `erb`, `builder` or `coffee`.
+Because you want to create a new HTML form, you will be using the `ERB`
+language. Therefore the file should be called `posts/new.html.erb` and needs to
+be located inside the `app/views` directory of the application.
+
+Go ahead now and create a new file at `app/views/posts/new.html.erb` and write
+this content in it:
```html
<h1>New Post</h1>
```
-When you refresh <http://localhost:3000/posts/new> you'll now see that the page has a title. The route, controller, action and view are now working harmoniously! It's time to create the form for a new post.
+When you refresh <http://localhost:3000/posts/new> you'll now see that the page
+has a title. The route, controller, action and view are now working
+harmoniously! It's time to create the form for a new post.
### The first form
@@ -381,14 +492,21 @@ method called `form_for`. To use this method, add this code into `app/views/post
<% end %>
```
-If you refresh the page now, you'll see the exact same form as in the example. Building forms in Rails is really just that easy!
+If you refresh the page now, you'll see the exact same form as in the example.
+Building forms in Rails is really just that easy!
When you call `form_for`, you pass it an identifying object for this
form. In this case, it's the symbol `:post`. This tells the `form_for`
helper what this form is for. Inside the block for this method, the
-`FormBuilder` object - represented by `f` - is used to build two labels and two text fields, one each for the title and text of a post. Finally, a call to `submit` on the `f` object will create a submit button for the form.
+`FormBuilder` object - represented by `f` - is used to build two labels and two
+text fields, one each for the title and text of a post. Finally, a call to
+`submit` on the `f` object will create a submit button for the form.
-There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the `action` attribute for the form is pointing at `/posts/new`. This is a problem because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new post.
+There's one problem with this form though. If you inspect the HTML that is
+generated, by viewing the source of the page, you will see that the `action`
+attribute for the form is pointing at `/posts/new`. This is a problem because
+this route goes to the very page that you're on right at the moment, and that
+route should only be used to display the form for a new post.
The form needs to use a different URL in order to go somewhere else.
This can be done quite simply with the `:url` option of `form_for`.
@@ -404,6 +522,7 @@ Edit the `form_for` line inside `app/views/posts/new.html.erb` to look like this
In this example, the `posts_path` helper is passed to the `:url` option.
To see what Rails will do with this, we look back at the output of
`rake routes`:
+
```bash
$ rake routes
Prefix Verb URI Pattern Controller#Action
@@ -417,21 +536,28 @@ edit_post GET /posts/:id/edit(.:format) posts#edit
DELETE /posts/:id(.:format) posts#destroy
root / welcome#index
```
+
The `posts_path` helper tells Rails to point the form
to the URI Pattern associated with the `posts` prefix; and
the form will (by default) send a `POST` request
to that route. This is associated with the
`create` action of the current controller, the `PostsController`.
-With the form and its associated route defined, you will be able to fill in the form and then click the submit button to begin the process of creating a new post, so go ahead and do that. When you submit the form, you should see a familiar error:
+With the form and its associated route defined, you will be able to fill in the
+form and then click the submit button to begin the process of creating a new
+post, so go ahead and do that. When you submit the form, you should see a
+familiar error:
![Unknown action create for PostsController](images/getting_started/unknown_action_create_for_posts.png)
-You now need to create the `create` action within the `PostsController` for this to work.
+You now need to create the `create` action within the `PostsController` for this
+to work.
### Creating posts
-To make the "Unknown action" go away, you can define a `create` action within the `PostsController` class in `app/controllers/posts_controller.rb`, underneath the `new` action:
+To make the "Unknown action" go away, you can define a `create` action within
+the `PostsController` class in `app/controllers/posts_controller.rb`, underneath
+the `new` action:
```ruby
class PostsController < ApplicationController
@@ -443,9 +569,14 @@ class PostsController < ApplicationController
end
```
-If you re-submit the form now, you'll see another familiar error: a template is missing. That's ok, we can ignore that for now. What the `create` action should be doing is saving our new post to a database.
+If you re-submit the form now, you'll see another familiar error: a template is
+missing. That's ok, we can ignore that for now. What the `create` action should
+be doing is saving our new post to a database.
-When a form is submitted, the fields of the form are sent to Rails as _parameters_. These parameters can then be referenced inside the controller actions, typically to perform a particular task. To see what these parameters look like, change the `create` action to this:
+When a form is submitted, the fields of the form are sent to Rails as
+_parameters_. These parameters can then be referenced inside the controller
+actions, typically to perform a particular task. To see what these parameters
+look like, change the `create` action to this:
```ruby
def create
@@ -453,15 +584,23 @@ def create
end
```
-The `render` method here is taking a very simple hash with a key of `text` and value of `params[:post].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns an `ActiveSupport::HashWithIndifferentAccess` object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form.
+The `render` method here is taking a very simple hash with a key of `text` and
+value of `params[:post].inspect`. The `params` method is the object which
+represents the parameters (or fields) coming in from the form. The `params`
+method returns an `ActiveSupport::HashWithIndifferentAccess` object, which
+allows you to access the keys of the hash using either strings or symbols. In
+this situation, the only parameters that matter are the ones from the form.
-If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following:
+If you re-submit the form one more time you'll now no longer get the missing
+template error. Instead, you'll see something that looks like the following:
```ruby
{"title"=>"First post!", "text"=>"This is my first post."}
```
-This action is now displaying the parameters for the post that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them.
+This action is now displaying the parameters for the post that are coming in
+from the form. However, this isn't really all that helpful. Yes, you can see the
+parameters but nothing in particular is being done with them.
### Creating the Post model
@@ -550,7 +689,7 @@ invoking the command: `rake db:migrate RAILS_ENV=production`.
### Saving data in the controller
-Back in `posts_controller`, we need to change the `create` action
+Back in `PostsController`, we need to change the `create` action
to use the new `Post` model to save the data in the database. Open `app/controllers/posts_controller.rb`
and change the `create` action to look like this:
@@ -603,8 +742,9 @@ private
See the `permit`? It allows us to accept both `title` and `text` in this
action.
-TIP: Note that `def post_params` is private. This new approach prevents an attacker from
-setting the model's attributes by manipulating the hash passed to the model.
+TIP: Note that `def post_params` is private. This new approach prevents an
+attacker from setting the model's attributes by manipulating the hash passed to
+the model.
For more information, refer to
[this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/).
@@ -614,7 +754,8 @@ If you submit the form again now, Rails will complain about not finding
the `show` action. That's not very useful though, so let's add the
`show` action before proceeding.
-As we have seen in the output of `rake routes`, the route for `show` action is as follows:
+As we have seen in the output of `rake routes`, the route for `show` action is
+as follows:
```ruby
post GET /posts/:id(.:format) posts#show
@@ -667,7 +808,7 @@ The route for this as per output of `rake routes` is:
posts GET /posts(.:format) posts#index
```
-And an action for that route inside the `PostsController` in the `app/controllers/posts_controller.rb` file:
+Add the corresponding `index` action for that route inside the `PostsController` in the `app/controllers/posts_controller.rb` file:
```ruby
def index
@@ -695,7 +836,8 @@ And then finally a view for this action, located at `app/views/posts/index.html.
</table>
```
-Now if you go to `http://localhost:3000/posts` you will see a list of all the posts that you have created.
+Now if you go to `http://localhost:3000/posts` you will see a list of all the
+posts that you have created.
### Adding links
@@ -706,20 +848,24 @@ Open `app/views/welcome/index.html.erb` and modify it as follows:
```html+erb
<h1>Hello, Rails!</h1>
-<%= link_to "My Blog", controller: "posts" %>
+<%= link_to 'My Blog', controller: 'posts' %>
```
The `link_to` method is one of Rails' built-in view helpers. It creates a
hyperlink based on text to display and where to go - in this case, to the path
for posts.
-Let's add links to the other views as well, starting with adding this "New Post" link to `app/views/posts/index.html.erb`, placing it above the `<table>` tag:
+Let's add links to the other views as well, starting with adding this "New Post"
+link to `app/views/posts/index.html.erb`, placing it above the `<table>` tag:
```erb
<%= link_to 'New post', new_post_path %>
```
-This link will allow you to bring up the form that lets you create a new post. You should also add a link to this template - `app/views/posts/new.html.erb` - to go back to the `index` action. Do this by adding this underneath the form in this template:
+This link will allow you to bring up the form that lets you create a new post.
+You should also add a link to this template - `app/views/posts/new.html.erb` -
+to go back to the `index` action. Do this by adding this underneath the form in
+this template:
```erb
<%= form_for :post do |f| %>
@@ -729,7 +875,9 @@ This link will allow you to bring up the form that lets you create a new post. Y
<%= link_to 'Back', posts_path %>
```
-Finally, add another link to the `app/views/posts/show.html.erb` template to go back to the `index` action as well, so that people who are viewing a single post can go back and view the whole list again:
+Finally, add another link to the `app/views/posts/show.html.erb` template to go
+back to the `index` action as well, so that people who are viewing a single post
+can go back and view the whole list again:
```html+erb
<p>
@@ -815,8 +963,11 @@ private
The `new` action is now creating a new instance variable called `@post`, and
you'll see why that is in just a few moments.
-Notice that inside the `create` action we use `render` instead of `redirect_to` when `save`
-returns `false`. The `render` method is used so that the `@post` object is passed back to the `new` template when it is rendered. This rendering is done within the same request as the form submission, whereas the `redirect_to` will tell the browser to issue another request.
+Notice that inside the `create` action we use `render` instead of `redirect_to`
+when `save` returns `false`. The `render` method is used so that the `@post`
+object is passed back to the `new` template when it is rendered. This rendering
+is done within the same request as the form submission, whereas the `redirect_to`
+will tell the browser to issue another request.
If you reload
<http://localhost:3000/posts/new> and
@@ -861,9 +1012,10 @@ A few things are going on. We check if there are any errors with
errors with `@post.errors.full_messages`.
`pluralize` is a rails helper that takes a number and a string as its
-arguments. If the number is greater than one, the string will be automatically pluralized.
+arguments. If the number is greater than one, the string will be automatically
+pluralized.
-The reason why we added `@post = Post.new` in `posts_controller` is that
+The reason why we added `@post = Post.new` in the `PostsController` is that
otherwise `@post` would be `nil` in our view, and calling
`@post.errors.any?` would throw an error.
@@ -878,9 +1030,10 @@ attempt to do just that on the new post form [(http://localhost:3000/posts/new)]
### Updating Posts
-We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating posts.
+We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating
+posts.
-The first step we'll take is adding an `edit` action to `posts_controller`.
+The first step we'll take is adding an `edit` action to the `PostsController`.
```ruby
def edit
@@ -981,7 +1134,7 @@ appear next to the "Show" link:
<tr>
<td><%= post.title %></td>
<td><%= post.text %></td>
- <td><%= link_to 'Show', post %></td>
+ <td><%= link_to 'Show', post_path(post) %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
</tr>
<% end %>
@@ -1053,8 +1206,8 @@ which URI and method to use.
For more information about this use of `form_for`, see
[Resource-oriented style](//api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style).
-Now, let's update the `app/views/posts/new.html.erb` view to use this new partial, rewriting it
-completely:
+Now, let's update the `app/views/posts/new.html.erb` view to use this new
+partial, rewriting it completely:
```html+erb
<h1>New post</h1>
@@ -1093,8 +1246,8 @@ people to craft malicious URLs like this:
```
We use the `delete` method for destroying resources, and this route is mapped to
-the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't exist yet, but is
-provided below:
+the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't
+exist yet, but is provided below:
```ruby
def destroy
@@ -1135,13 +1288,14 @@ together.
</table>
```
-Here we're using `link_to` in a different way. We pass the named route as the second argument,
-and then the options as another argument. The `:method` and `:'data-confirm'`
-options are used as HTML5 attributes so that when the link is clicked,
-Rails will first show a confirm dialog to the user, and then submit the link with method `delete`.
-This is done via the JavaScript file `jquery_ujs` which is automatically included
-into your application's layout (`app/views/layouts/application.html.erb`) when you
-generated the application. Without this file, the confirmation dialog box wouldn't appear.
+Here we're using `link_to` in a different way. We pass the named route as the
+second argument, and then the options as another argument. The `:method` and
+`:'data-confirm'` options are used as HTML5 attributes so that when the link is
+clicked, Rails will first show a confirm dialog to the user, and then submit the
+link with method `delete`. This is done via the JavaScript file `jquery_ujs`
+which is automatically included into your application's layout
+(`app/views/layouts/application.html.erb`) when you generated the application.
+Without this file, the confirmation dialog box wouldn't appear.
![Confirm Dialog](images/getting_started/confirm_dialog.png)
@@ -1156,8 +1310,8 @@ For more information about routing, see
Adding a Second Model
---------------------
-It's time to add a second model to the application. The second model will handle comments on
-posts.
+It's time to add a second model to the application. The second model will handle
+comments on posts.
### Generating a Model
@@ -1186,7 +1340,7 @@ class Comment < ActiveRecord::Base
end
```
-This is very similar to the `post.rb` model that you saw earlier. The difference
+This is very similar to the `Post` model that you saw earlier. The difference
is the line `belongs_to :post`, which sets up an Active Record _association_.
You'll learn a little about associations in the next section of this guide.
@@ -1235,8 +1389,8 @@ this way:
* One post can have many comments.
In fact, this is very close to the syntax that Rails uses to declare this
-association. You've already seen the line of code inside the `Comment` model (app/models/comment.rb) that
-makes each comment belong to a Post:
+association. You've already seen the line of code inside the `Comment` model
+(app/models/comment.rb) that makes each comment belong to a Post:
```ruby
class Comment < ActiveRecord::Base
@@ -1336,8 +1490,8 @@ So first, we'll wire up the Post show template
</p>
<% end %>
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %>
+<%= link_to 'Back', posts_path %>
+| <%= link_to 'Edit', edit_post_path(@post) %>
```
This adds a form on the `Post` show page that creates a new comment by
@@ -1664,6 +1818,7 @@ class CommentsController < ApplicationController
@post = Post.find(params[:post_id])
...
end
+
# snipped for brevity
```
@@ -1681,7 +1836,7 @@ along with a number of others.
### Other Security Considerations
Security, especially in web applications, is a broad and detailed area. Security
-in your Rails application is covered in more depth in
+in your Rails application is covered in more depth in
The [Ruby on Rails Security Guide](security.html)
@@ -1698,12 +1853,19 @@ free to consult these support resources:
* The [Ruby on Rails mailing list](http://groups.google.com/group/rubyonrails-talk)
* The [#rubyonrails](irc://irc.freenode.net/#rubyonrails) channel on irc.freenode.net
-Rails also comes with built-in help that you can generate using the rake command-line utility:
+Rails also comes with built-in help that you can generate using the rake
+command-line utility:
-* Running `rake doc:guides` will put a full copy of the Rails Guides in the `doc/guides` folder of your application. Open `doc/guides/index.html` in your web browser to explore the Guides.
-* Running `rake doc:rails` will put a full copy of the API documentation for Rails in the `doc/api` folder of your application. Open `doc/api/index.html` in your web browser to explore the API documentation.
+* Running `rake doc:guides` will put a full copy of the Rails Guides in the
+ `doc/guides` folder of your application. Open `doc/guides/index.html` in your
+ web browser to explore the Guides.
+* Running `rake doc:rails` will put a full copy of the API documentation for
+ Rails in the `doc/api` folder of your application. Open `doc/api/index.html`
+ in your web browser to explore the API documentation.
-TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake task you need to install the RedCloth gem. Add it to your `Gemfile` and run `bundle install` and you're ready to go.
+TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake
+task you need to install the RedCloth gem. Add it to your `Gemfile` and run
+`bundle install` and you're ready to go.
Configuration Gotchas
---------------------
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index ead9c6b94d..33daa79133 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -28,7 +28,7 @@ After reading this guide, you will know:
--------------------------------------------------------------------------------
-NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Rails [I18n Wiki](http://rails-i18n.org/wiki) for more information.
+NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Ruby [I18n Wiki](http://ruby-i18n.org/wiki) for more information.
How I18n in Ruby on Rails Works
-------------------------------
@@ -282,10 +282,11 @@ def set_locale
I18n.locale = extract_locale_from_accept_language_header
logger.debug "* Locale set to '#{I18n.locale}'"
end
+
private
-def extract_locale_from_accept_language_header
- request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
-end
+ def extract_locale_from_accept_language_header
+ request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
+ end
```
Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb).
@@ -315,6 +316,17 @@ end
```
```ruby
+# app/controllers/application_controller.rb
+class ApplicationController < ActionController::Base
+ before_action :set_locale
+
+ def set_locale
+ I18n.locale = params[:locale] || I18n.default_locale
+ end
+end
+```
+
+```ruby
# app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
@@ -742,7 +754,7 @@ en:
other: Dudes
```
-Then `User.model_name.human(:count => 2)` will return "Dudes". With `:count => 1` or without params will return "Dude".
+Then `User.model_name.human(count: 2)` will return "Dudes". With `count: 1` or without params will return "Dude".
#### Error Message Scopes
@@ -831,6 +843,7 @@ So, for example, instead of the default error message `"can not be blank"` you c
| numericality | :equal_to | :equal_to | count |
| numericality | :less_than | :less_than | count |
| numericality | :less_than_or_equal_to | :less_than_or_equal_to | count |
+| numericality | :only_integer | :not_an_integer | - |
| numericality | :odd | :odd | - |
| numericality | :even | :even | - |
@@ -883,15 +896,15 @@ Rails uses fixed strings and other localizations, such as format strings and oth
#### Action View Helper Methods
-* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L51) translations.
+* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L4) translations.
-* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L83) scope if applicable.
+* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L39) scope if applicable.
-* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L2) scope.
+* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L37) scope.
#### Active Model Methods
-* `model_name.human` and `human_attribute_name` use translations for model names and attribute names if available in the [activerecord.models](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L29) scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes".
+* `model_name.human` and `human_attribute_name` use translations for model names and attribute names if available in the [activerecord.models](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L36) scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes".
* `ActiveModel::Errors#generate_message` (which is used by Active Model validations but may also be used manually) uses `model_name.human` and `human_attribute_name` (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".
@@ -899,7 +912,7 @@ Rails uses fixed strings and other localizations, such as format strings and oth
#### Active Support Methods
-* `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30) scope.
+* `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33) scope.
Customize your I18n Setup
-------------------------
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index c78eef5bf0..fe6b1ad906 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -217,12 +217,12 @@ With the `default_options` set to this:
```ruby
def default_options
{
- :environment => ENV['RACK_ENV'] || "development",
- :pid => nil,
- :Port => 9292,
- :Host => "0.0.0.0",
- :AccessLog => [],
- :config => "config.ru"
+ environment: ENV['RACK_ENV'] || "development",
+ pid: nil,
+ Port: 9292,
+ Host: "0.0.0.0",
+ AccessLog: [],
+ config: "config.ru"
}
end
```
@@ -468,14 +468,24 @@ def initialize!(group=:default) #:nodoc:
end
```
-As you can see, you can only initialize an app once. This is also where the initializers are run.
+As you can see, you can only initialize an app once. The initializers are run through
+the `run_initializers` method which is defined in `railties/lib/rails/initializable.rb`
-TODO: review this
+```ruby
+def run_initializers(group=:default, *args)
+ return if instance_variable_defined?(:@ran)
+ initializers.tsort_each do |initializer|
+ initializer.run(*args) if initializer.belongs_to?(group)
+ end
+ @ran = true
+end
+```
-The initializers code itself is tricky. What Rails is doing here is it
-traverses all the class ancestors looking for an `initializers` method,
-sorting them and running them. For example, the `Engine` class will make
-all the engines available by providing the `initializers` method.
+The run_initializers code itself is tricky. What Rails is doing here is
+traversing all the class ancestors looking for those that respond to an
+`initializers` method. It then sorts the ancestors by name, and runs them.
+For example, the `Engine` class will make all the engines available by
+providing an `initializers` method on them.
The `Rails::Application` class, as defined in `railties/lib/rails/application.rb`
defines `bootstrap`, `railtie`, and `finisher` initializers. The `bootstrap` initializers
diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md
new file mode 100644
index 0000000000..93729c6f72
--- /dev/null
+++ b/guides/source/maintenance_policy.md
@@ -0,0 +1,56 @@
+Maintenance Policy for Ruby on Rails
+====================================
+
+Support of the Rails framework is divided into four groups: New features, bug
+fixes, security issues, and severe security issues. They are handled as
+follows, all versions in x.y.z format
+
+--------------------------------------------------------------------------------
+
+New Features
+------------
+
+New features are only added to the master branch and will not be made available
+in point releases.
+
+Bug Fixes
+---------
+
+Only the latest release series will receive bug fixes. When enough bugs are
+fixed and its deemed worthy to release a new gem, this is the branch it happens
+from.
+
+**Currently included series:** 4.0.z
+
+Security Issues
+---------------
+
+The current release series and the next most recent one will receive patches
+and new versions in case of a security issue.
+
+These releases are created by taking the last released version, applying the
+security patches, and releasing. Those patches are then applied to the end of
+the x-y-stable branch. For example, a theoretical 1.2.3 security release would
+be built from 1.2.2, and then added to the end of 1-2-stable. This means that
+security releases are easy to upgrade to if you're running the latest version
+of Rails.
+
+**Currently included series:** 4.0.z, 3.2.z
+
+Severe Security Issues
+----------------------
+
+For severe security issues we will provide new versions as above, and also the
+last major release series will receive patches and new versions. The
+classification of the security issue is judged by the core team.
+
+**Currently included series:** 4.0.z, 3.2.z
+
+Unsupported Release Series
+--------------------------
+
+When a release series is no longer supported, it's your own responsibility to
+deal with bugs and security issues. We may provide backports of the fixes and
+publish them to git, however there will be no new versions released. If you are
+not comfortable maintaining your own versions, you should upgrade to a
+supported version.
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index 414ce46a4e..b7283d16cc 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -184,7 +184,7 @@ class RemovePartNumberFromProducts < ActiveRecord::Migration
end
```
-You are not limited to one magically generated column. For example
+You are not limited to one magically generated column. For example:
```bash
$ rails generate migration AddDetailsToProducts part_number:string price:decimal
@@ -227,7 +227,7 @@ or remove from it as you see fit by editing the
`db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file.
Also, the generator accepts column type as `references`(also available as
-`belongs_to`). For instance
+`belongs_to`). For instance:
```bash
$ rails generate migration AddUserRefToProducts user:references
@@ -269,7 +269,7 @@ end
The model and scaffold generators will create migrations appropriate for adding
a new model. This migration will already contain instructions for creating the
relevant table. If you tell Rails what columns you want, then statements for
-adding these columns will also be created. For example, running
+adding these columns will also be created. For example, running:
```bash
$ rails generate model Product name:string description:text
@@ -301,11 +301,12 @@ braces. You can use the following modifiers:
* `precision` Defines the precision for the `decimal` fields
* `scale` Defines the scale for the `decimal` fields
* `polymorphic` Adds a `type` column for `belongs_to` associations
+* `null` Allows or disallows `NULL` values in the column.
-For instance, running
+For instance, running:
```bash
-$ rails generate migration AddDetailsToProducts price:decimal{5,2} supplier:references{polymorphic}
+$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
```
will produce a migration that looks like this
@@ -313,7 +314,7 @@ will produce a migration that looks like this
```ruby
class AddDetailsToProducts < ActiveRecord::Migration
def change
- add_column :products, :price, precision: 5, scale: 2
+ add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true, index: true
end
end
@@ -344,7 +345,7 @@ By default, `create_table` will create a primary key called `id`. You can change
the name of the primary key with the `:primary_key` option (don't forget to
update the corresponding model) or, if you don't want a primary key at all, you
can pass the option `id: false`. If you need to pass database specific options
-you can place an SQL fragment in the `:options` option. For example,
+you can place an SQL fragment in the `:options` option. For example:
```ruby
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
@@ -358,7 +359,7 @@ will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
### Creating a Join Table
Migration method `create_join_table` creates a HABTM join table. A typical use
-would be
+would be:
```ruby
create_join_table :products, :categories
@@ -377,7 +378,7 @@ will create the `product_id` and `category_id` with the `:null` option as
`true`.
You can pass the option `:table_name` when you want to customize the table
-name. For example,
+name. For example:
```ruby
create_join_table :products, :categories, table_name: :categorization
@@ -399,7 +400,7 @@ end
A close cousin of `create_table` is `change_table`, used for changing existing
tables. It is used in a similar fashion to `create_table` but the object
-yielded to the block knows more tricks. For example
+yielded to the block knows more tricks. For example:
```ruby
change_table :products do |t|
@@ -463,7 +464,7 @@ or write the `up` and `down` methods instead of using the `change` method.
Complex migrations may require processing that Active Record doesn't know how
to reverse. You can use `reversible` to specify what to do when running a
-migration what else to do when reverting it. For example,
+migration what else to do when reverting it. For example:
```ruby
class ExampleMigration < ActiveRecord::Migration
@@ -647,7 +648,7 @@ will update your `db/schema.rb` file to match the structure of your database.
If you specify a target version, Active Record will run the required migrations
(change, up, down) until it has reached the specified version. The version
is the numerical prefix on the migration's filename. For example, to migrate
-to version 20080906120000 run
+to version 20080906120000 run:
```bash
$ rake db:migrate VERSION=20080906120000
@@ -664,7 +665,7 @@ down to, but not including, 20080906120000.
A common task is to rollback the last migration. For example, if you made a
mistake in it and wish to correct it. Rather than tracking down the version
-number associated with the previous migration you can run
+number associated with the previous migration you can run:
```bash
$ rake db:rollback
@@ -682,7 +683,7 @@ will revert the last 3 migrations.
The `db:migrate:redo` task is a shortcut for doing a rollback and then migrating
back up again. As with the `db:rollback` task, you can use the `STEP` parameter
-if you need to go more than one version back, for example
+if you need to go more than one version back, for example:
```bash
$ rake db:migrate:redo STEP=3
@@ -703,16 +704,16 @@ The `rake db:reset` task will drop the database and set it up again. This is
functionally equivalent to `rake db:drop db:setup`.
NOTE: This is not the same as running all the migrations. It will only use the
-contents of the current schema.rb file. If a migration can't be rolled back,
-'rake db:reset' may not help you. To find out more about dumping the schema see
-'[schema dumping and you](#schema-dumping-and-you).'
+contents of the current `schema.rb` file. If a migration can't be rolled back,
+`rake db:reset` may not help you. To find out more about dumping the schema see
+[Schema Dumping and You](#schema-dumping-and-you) section.
### Running Specific Migrations
If you need to run a specific migration up or down, the `db:migrate:up` and
`db:migrate:down` tasks will do that. Just specify the appropriate version and
the corresponding migration will have its `change`, `up` or `down` method
-invoked, for example,
+invoked, for example:
```bash
$ rake db:migrate:up VERSION=20080906120000
@@ -754,7 +755,7 @@ Several methods are provided in migrations that allow you to control all this:
| say | Takes a message argument and outputs it as is. A second boolean argument can be passed to specify whether to indent or not.
| say_with_time | Outputs text along with how long it took to run its block. If the block returns an integer it assumes it is the number of rows affected.
-For example, this migration
+For example, this migration:
```ruby
class CreateProducts < ActiveRecord::Migration
@@ -1039,8 +1040,8 @@ this, then you should set the schema format to `:sql`.
Instead of using Active Record's schema dumper, the database's structure will
be dumped using a tool specific to the database (via the `db:structure:dump`
Rake task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump`
-utility is used. For MySQL, this file will contain the output of `SHOW CREATE
-TABLE` for the various tables.
+utility is used. For MySQL, this file will contain the output of
+`SHOW CREATE TABLE` for the various tables.
Loading these schemas is simply a question of executing the SQL statements they
contain. By definition, this will create a perfect copy of the database's
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index b52504b104..d0aa2e55a2 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -15,7 +15,7 @@ After reading this guide, you will know:
This guide describes how to build a test-driven plugin that will:
* Extend core Ruby classes like Hash and String.
-* Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins.
+* Add methods to `ActiveRecord::Base` in the tradition of the `acts_as` plugins.
* Give you information about where to put generators in your plugin.
For the purpose of this guide pretend for a moment that you are an avid bird watcher.
@@ -34,9 +34,15 @@ different rails applications using RubyGems and Bundler if desired.
Rails ships with a `rails plugin new` command which creates a
- skeleton for developing any kind of Rails extension with the ability
- to run integration tests using a dummy Rails application. See usage
- and options by asking for help:
+skeleton for developing any kind of Rails extension with the ability
+to run integration tests using a dummy Rails application. Create your
+plugin with the command:
+
+```bash
+$ rails plugin new yaffle
+```
+
+See usage and options by asking for help:
```bash
$ rails plugin --help
@@ -126,8 +132,8 @@ $ rails console
Add an "acts_as" Method to Active Record
----------------------------------------
-A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you
-want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your Active Record models.
+A common pattern in plugins is to add a method called `acts_as_something` to models. In this case, you
+want to write a method called `acts_as_yaffle` that adds a `squawk` method to your Active Record models.
To begin, set up your files so that you have:
@@ -162,9 +168,9 @@ end
### Add a Class Method
-This plugin will expect that you've added a method to your model named 'last_squawk'. However, the
-plugin users might have already defined a method on their model named 'last_squawk' that they use
-for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'.
+This plugin will expect that you've added a method to your model named `last_squawk`. However, the
+plugin users might have already defined a method on their model named `last_squawk` that they use
+for something else. This plugin will allow the name to be changed by adding a class method called `yaffle_text_field`.
To start out, write a failing test that shows the behavior you'd like:
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 642c70fd9d..d53e0cd2bd 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -225,9 +225,13 @@ config.middleware.delete "Rack::MethodOverride"
Much of Action Controller's functionality is implemented as Middlewares. The following list explains the purpose of each of them:
+ **`Rack::Sendfile`**
+
+* Sets server specific X-Sendfile header. Configure this via `config.action_dispatch.x_sendfile_header` option.
+
**`ActionDispatch::Static`**
-* Used to serve static assets. Disabled if `config.serve_static_assets` is true.
+* Used to serve static assets. Disabled if `config.serve_static_assets` is `false`.
**`Rack::Lock`**
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 76c4c25108..19784823f7 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -89,15 +89,15 @@ resources :photos
creates seven different routes in your application, all mapping to the `Photos` controller:
-| HTTP Verb | Path | Action | Used for |
-| --------- | ---------------- | ------- | -------------------------------------------- |
-| GET | /photos | index | display a list of all photos |
-| GET | /photos/new | new | return an HTML form for creating a new photo |
-| POST | /photos | create | create a new photo |
-| GET | /photos/:id | show | display a specific photo |
-| GET | /photos/:id/edit | edit | return an HTML form for editing a photo |
-| PATCH/PUT | /photos/:id | update | update a specific photo |
-| DELETE | /photos/:id | destroy | delete a specific photo |
+| HTTP Verb | Path | Controller#Action | Used for |
+| --------- | ---------------- | ----------------- | -------------------------------------------- |
+| GET | /photos | photos#index | display a list of all photos |
+| GET | /photos/new | photos#new | return an HTML form for creating a new photo |
+| POST | /photos | photos#create | create a new photo |
+| GET | /photos/:id | photos#show | display a specific photo |
+| GET | /photos/:id/edit | photos#edit | return an HTML form for editing a photo |
+| PATCH/PUT | /photos/:id | photos#update | update a specific photo |
+| DELETE | /photos/:id | photos#destroy | delete a specific photo |
NOTE: Because the router uses the HTTP verb and URL to match inbound requests, four URLs map to seven different actions.
@@ -152,14 +152,14 @@ resource :geocoder
creates six different routes in your application, all mapping to the `Geocoders` controller:
-| HTTP Verb | Path | Action | Used for |
-| --------- | -------------- | ------- | --------------------------------------------- |
-| GET | /geocoder/new | new | return an HTML form for creating the geocoder |
-| POST | /geocoder | create | create the new geocoder |
-| GET | /geocoder | show | display the one and only geocoder resource |
-| GET | /geocoder/edit | edit | return an HTML form for editing the geocoder |
-| PATCH/PUT | /geocoder | update | update the one and only geocoder resource |
-| DELETE | /geocoder | destroy | delete the geocoder resource |
+| HTTP Verb | Path | Controller#Action | Used for |
+| --------- | -------------- | ----------------- | --------------------------------------------- |
+| GET | /geocoder/new | geocoders#new | return an HTML form for creating the geocoder |
+| POST | /geocoder | geocoders#create | create the new geocoder |
+| GET | /geocoder | geocoders#show | display the one and only geocoder resource |
+| GET | /geocoder/edit | geocoders#edit | return an HTML form for editing the geocoder |
+| PATCH/PUT | /geocoder | geocoders#update | update the one and only geocoder resource |
+| DELETE | /geocoder | geocoders#destroy | delete the geocoder resource |
NOTE: Because you might want to use the same controller for a singular route (`/account`) and a plural route (`/accounts/45`), singular resources map to plural controllers. So that, for example, `resource :photo` and `resources :photos` creates both singular and plural routes that map to the same controller (`PhotosController`).
@@ -189,15 +189,15 @@ end
This will create a number of routes for each of the `posts` and `comments` controller. For `Admin::PostsController`, Rails will create:
-| HTTP Verb | Path | Action | Used for |
-| --------- | --------------------- | ------- | ------------------------- |
-| GET | /admin/posts | index | admin_posts_path |
-| GET | /admin/posts/new | new | new_admin_post_path |
-| POST | /admin/posts | create | admin_posts_path |
-| GET | /admin/posts/:id | show | admin_post_path(:id) |
-| GET | /admin/posts/:id/edit | edit | edit_admin_post_path(:id) |
-| PATCH/PUT | /admin/posts/:id | update | admin_post_path(:id) |
-| DELETE | /admin/posts/:id | destroy | admin_post_path(:id) |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | --------------------- | ------------------- | ------------------------- |
+| GET | /admin/posts | admin/posts#index | admin_posts_path |
+| GET | /admin/posts/new | admin/posts#new | new_admin_post_path |
+| POST | /admin/posts | admin/posts#create | admin_posts_path |
+| GET | /admin/posts/:id | admin/posts#show | admin_post_path(:id) |
+| GET | /admin/posts/:id/edit | admin/posts#edit | edit_admin_post_path(:id) |
+| PATCH/PUT | /admin/posts/:id | admin/posts#update | admin_post_path(:id) |
+| DELETE | /admin/posts/:id | admin/posts#destroy | admin_post_path(:id) |
If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use:
@@ -229,15 +229,15 @@ resources :posts, path: '/admin/posts'
In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`:
-| HTTP Verb | Path | Action | Named Helper |
-| --------- | --------------------- | ------- | ------------------- |
-| GET | /admin/posts | index | posts_path |
-| GET | /admin/posts/new | new | new_post_path |
-| POST | /admin/posts | create | posts_path |
-| GET | /admin/posts/:id | show | post_path(:id) |
-| GET | /admin/posts/:id/edit | edit | edit_post_path(:id) |
-| PATCH/PUT | /admin/posts/:id | update | post_path(:id) |
-| DELETE | /admin/posts/:id | destroy | post_path(:id) |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | --------------------- | ----------------- | ------------------- |
+| GET | /admin/posts | posts#index | posts_path |
+| GET | /admin/posts/new | posts#new | new_post_path |
+| POST | /admin/posts | posts#create | posts_path |
+| GET | /admin/posts/:id | posts#show | post_path(:id) |
+| GET | /admin/posts/:id/edit | posts#edit | edit_post_path(:id) |
+| PATCH/PUT | /admin/posts/:id | posts#update | post_path(:id) |
+| DELETE | /admin/posts/:id | posts#destroy | post_path(:id) |
### Nested Resources
@@ -263,15 +263,15 @@ end
In addition to the routes for magazines, this declaration will also route ads to an `AdsController`. The ad URLs require a magazine:
-| HTTP Verb | Path | Action | Used for |
-| --------- | ------------------------------------ | ------- | -------------------------------------------------------------------------- |
-| GET | /magazines/:magazine_id/ads | index | display a list of all ads for a specific magazine |
-| GET | /magazines/:magazine_id/ads/new | new | return an HTML form for creating a new ad belonging to a specific magazine |
-| POST | /magazines/:magazine_id/ads | create | create a new ad belonging to a specific magazine |
-| GET | /magazines/:magazine_id/ads/:id | show | display a specific ad belonging to a specific magazine |
-| GET | /magazines/:magazine_id/ads/:id/edit | edit | return an HTML form for editing an ad belonging to a specific magazine |
-| PATCH/PUT | /magazines/:magazine_id/ads/:id | update | update a specific ad belonging to a specific magazine |
-| DELETE | /magazines/:magazine_id/ads/:id | destroy | delete a specific ad belonging to a specific magazine |
+| HTTP Verb | Path | Controller#Action | Used for |
+| --------- | ------------------------------------ | ----------------- | -------------------------------------------------------------------------- |
+| GET | /magazines/:magazine_id/ads | ads#index | display a list of all ads for a specific magazine |
+| GET | /magazines/:magazine_id/ads/new | ads#new | return an HTML form for creating a new ad belonging to a specific magazine |
+| POST | /magazines/:magazine_id/ads | ads#create | create a new ad belonging to a specific magazine |
+| GET | /magazines/:magazine_id/ads/:id | ads#show | display a specific ad belonging to a specific magazine |
+| GET | /magazines/:magazine_id/ads/:id/edit | ads#edit | return an HTML form for editing an ad belonging to a specific magazine |
+| PATCH/PUT | /magazines/:magazine_id/ads/:id | ads#update | update a specific ad belonging to a specific magazine |
+| DELETE | /magazines/:magazine_id/ads/:id | ads#destroy | delete a specific ad belonging to a specific magazine |
This will also create routing helpers such as `magazine_ads_url` and `edit_magazine_ad_path`. These helpers take an instance of Magazine as the first parameter (`magazine_ads_url(@magazine)`).
@@ -350,15 +350,15 @@ end
The comments resource here will have the following routes generated for it:
-| HTTP Verb | Path | Named Helper |
-| --------- | -------------------------------------- | ------------------- |
-| GET | /posts/:post_id/comments(.:format) | post_comments |
-| POST | /posts/:post_id/comments(.:format) | post_comments |
-| GET | /posts/:post_id/comments/new(.:format) | new_post_comment |
-| GET | /sekret/comments/:id/edit(.:format) | edit_comment |
-| GET | /sekret/comments/:id(.:format) | comment |
-| PATCH/PUT | /sekret/comments/:id(.:format) | comment |
-| DELETE | /sekret/comments/:id(.:format) | comment |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | -------------------------------------- | ----------------- | ------------------- |
+| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments |
+| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments |
+| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment |
+| GET | /sekret/comments/:id/edit(.:format) | comments#edit | edit_comment |
+| GET | /sekret/comments/:id(.:format) | comments#show | comment |
+| PATCH/PUT | /sekret/comments/:id(.:format) | comments#update | comment |
+| DELETE | /sekret/comments/:id(.:format) | comments#destroy | comment |
The `:shallow_prefix` option adds the specified parameter to the named helpers:
@@ -372,15 +372,15 @@ end
The comments resource here will have the following routes generated for it:
-| HTTP Verb | Path | Named Helper |
-| --------- | -------------------------------------- | ------------------- |
-| GET | /posts/:post_id/comments(.:format) | post_comments |
-| POST | /posts/:post_id/comments(.:format) | post_comments |
-| GET | /posts/:post_id/comments/new(.:format) | new_post_comment |
-| GET | /comments/:id/edit(.:format) | edit_sekret_comment |
-| GET | /comments/:id(.:format) | sekret_comment |
-| PATCH/PUT | /comments/:id(.:format) | sekret_comment |
-| DELETE | /comments/:id(.:format) | sekret_comment |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | -------------------------------------- | ----------------- | ------------------- |
+| GET | /posts/:post_id/comments(.:format) | comments#index | post_comments |
+| POST | /posts/:post_id/comments(.:format) | comments#create | post_comments |
+| GET | /posts/:post_id/comments/new(.:format) | comments#new | new_post_comment |
+| GET | /comments/:id/edit(.:format) | comments#edit | edit_sekret_comment |
+| GET | /comments/:id(.:format) | comments#show | sekret_comment |
+| PATCH/PUT | /comments/:id(.:format) | comments#update | sekret_comment |
+| DELETE | /comments/:id(.:format) | comments#destroy | sekret_comment |
### Routing concerns
@@ -485,7 +485,10 @@ end
This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`, with the resource id value passed in `params[:id]`. It will also create the `preview_photo_url` and `preview_photo_path` helpers.
-Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use `get`, `patch`, `put`, `post`, or `delete` here. If you don't have multiple `member` routes, you can also pass `:on` to a route, eliminating the block:
+Within the block of member routes, each route name specifies the HTTP verb
+will be recognized. You can use `get`, `patch`, `put`, `post`, or `delete` here
+. If you don't have multiple `member` routes, you can also pass `:on` to a
+route, eliminating the block:
```ruby
resources :photos do
@@ -842,15 +845,15 @@ resources :photos, controller: 'images'
will recognize incoming paths beginning with `/photos` but route to the `Images` controller:
-| HTTP Verb | Path | Action | Named Helper |
-| --------- | ---------------- | ------- | -------------------- |
-| GET | /photos | index | photos_path |
-| GET | /photos/new | new | new_photo_path |
-| POST | /photos | create | photos_path |
-| GET | /photos/:id | show | photo_path(:id) |
-| GET | /photos/:id/edit | edit | edit_photo_path(:id) |
-| PATCH/PUT | /photos/:id | update | photo_path(:id) |
-| DELETE | /photos/:id | destroy | photo_path(:id) |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | ---------------- | ----------------- | -------------------- |
+| GET | /photos | images#index | photos_path |
+| GET | /photos/new | images#new | new_photo_path |
+| POST | /photos | images#create | photos_path |
+| GET | /photos/:id | images#show | photo_path(:id) |
+| GET | /photos/:id/edit | images#edit | edit_photo_path(:id) |
+| PATCH/PUT | /photos/:id | images#update | photo_path(:id) |
+| DELETE | /photos/:id | images#destroy | photo_path(:id) |
NOTE: Use `photos_path`, `new_photo_path`, etc. to generate paths for this resource.
@@ -863,8 +866,8 @@ resources :user_permissions, controller: 'admin/user_permissions'
This will route to the `Admin::UserPermissions` controller.
NOTE: Only the directory notation is supported. Specifying the
-controller with Ruby constant notation (eg. `:controller =>
-'Admin::UserPermissions'`) can lead to routing problems and results in
+controller with Ruby constant notation (eg. `controller: 'Admin::UserPermissions'`)
+can lead to routing problems and results in
a warning.
### Specifying Constraints
@@ -900,15 +903,15 @@ resources :photos, as: 'images'
will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the :as option to name the helpers.
-| HTTP Verb | Path | Action | Named Helper |
-| --------- | ---------------- | ------- | -------------------- |
-| GET | /photos | index | images_path |
-| GET | /photos/new | new | new_image_path |
-| POST | /photos | create | images_path |
-| GET | /photos/:id | show | image_path(:id) |
-| GET | /photos/:id/edit | edit | edit_image_path(:id) |
-| PATCH/PUT | /photos/:id | update | image_path(:id) |
-| DELETE | /photos/:id | destroy | image_path(:id) |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | ---------------- | ----------------- | -------------------- |
+| GET | /photos | photos#index | images_path |
+| GET | /photos/new | photos#new | new_image_path |
+| POST | /photos | photos#create | images_path |
+| GET | /photos/:id | photos#show | image_path(:id) |
+| GET | /photos/:id/edit | photos#edit | edit_image_path(:id) |
+| PATCH/PUT | /photos/:id | photos#update | image_path(:id) |
+| DELETE | /photos/:id | photos#destroy | image_path(:id) |
### Overriding the `new` and `edit` Segments
@@ -1005,15 +1008,15 @@ end
Rails now creates routes to the `CategoriesController`.
-| HTTP Verb | Path | Action | Used for |
-| --------- | -------------------------- | ------- | ----------------------- |
-| GET | /kategorien | index | categories_path |
-| GET | /kategorien/neu | new | new_category_path |
-| POST | /kategorien | create | categories_path |
-| GET | /kategorien/:id | show | category_path(:id) |
-| GET | /kategorien/:id/bearbeiten | edit | edit_category_path(:id) |
-| PATCH/PUT | /kategorien/:id | update | category_path(:id) |
-| DELETE | /kategorien/:id | destroy | category_path(:id) |
+| HTTP Verb | Path | Controller#Action | Named Helper |
+| --------- | -------------------------- | ------------------ | ----------------------- |
+| GET | /kategorien | categories#index | categories_path |
+| GET | /kategorien/neu | categories#new | new_category_path |
+| POST | /kategorien | categories#create | categories_path |
+| GET | /kategorien/:id | categories#show | category_path(:id) |
+| GET | /kategorien/:id/bearbeiten | categories#edit | edit_category_path(:id) |
+| PATCH/PUT | /kategorien/:id | categories#update | category_path(:id) |
+| DELETE | /kategorien/:id | categories#destroy | category_path(:id) |
### Overriding the Singular Form
diff --git a/guides/source/security.md b/guides/source/security.md
index 97b7355771..d7a41497f8 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -290,7 +290,7 @@ NOTE: _Make sure file uploads don't overwrite important files, and process media
Many web applications allow users to upload files. _File names, which the user may choose (partly), should always be filtered_ as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like "../../../etc/passwd", it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so - one more reason to run web servers, database servers and other programs as a less privileged Unix user.
-When filtering user input file names, _don't try to remove malicious parts_. Think of a situation where the web application removes all "../" in a file name and an attacker uses a string such as "....//" - the result will be "../". It is best to use a whitelist approach, which _checks for the validity of a file name with a set of accepted characters_. This is opposed to a blacklist approach which attempts to remove not allowed characters. In case it isn't a valid file name, reject it (or replace not accepted characters), but don't remove them. Here is the file name sanitizer from the [attachment_fu plugin](https://github.com/technoweenie/attachment_fu/tree/master:)
+When filtering user input file names, _don't try to remove malicious parts_. Think of a situation where the web application removes all "../" in a file name and an attacker uses a string such as "....//" - the result will be "../". It is best to use a whitelist approach, which _checks for the validity of a file name with a set of accepted characters_. This is opposed to a blacklist approach which attempts to remove not allowed characters. In case it isn't a valid file name, reject it (or replace not accepted characters), but don't remove them. Here is the file name sanitizer from the [attachment_fu plugin](https://github.com/technoweenie/attachment_fu/tree/master):
```ruby
def sanitize_filename(filename)
@@ -447,7 +447,7 @@ Here are some ideas how to hide honeypot fields by JavaScript and/or CSS:
The most simple negative CAPTCHA is one hidden honeypot field. On the server side, you will check the value of the field: If it contains any text, it must be a bot. Then, you can either ignore the post or return a positive result, but not saving the post to the database. This way the bot will be satisfied and moves on. You can do this with annoying users, too.
-You can find more sophisticated negative CAPTCHAs in Ned Batchelder's [blog post](http://nedbatchelder.com/text/stopbots.html:)
+You can find more sophisticated negative CAPTCHAs in Ned Batchelder's [blog post](http://nedbatchelder.com/text/stopbots.html):
* Include a field with the current UTC time-stamp in it and check it on the server. If it is too far in the past, or if it is in the future, the form is invalid.
* Randomize the field names
@@ -481,7 +481,7 @@ A good password is a long alphanumeric combination of mixed cases. As this is qu
INFO: _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._
-Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books make this wrong. So how is this a security threat? Say you wanted to loosely validate a URL field and you used a simple regular expression like this:
+Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books get this wrong. So how is this a security threat? Say you wanted to loosely validate a URL field and you used a simple regular expression like this:
```ruby
/^https?:\/\/[^\n]+$/i
@@ -760,7 +760,7 @@ The following is an excerpt from the [Js.Yamanner@m](http://www.symantec.com/sec
The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application.
-Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/article/9/Paper_A_PoC_of_a_cross_webmail_worm_XWW_called_Njuda_connection/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.
+Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/news/37/Nduja_Connection_A_cross_webmail_worm_XWW/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.
In December 2006, 34,000 actual user names and passwords were stolen in a [MySpace phishing attack](http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html). The idea of the attack was to create a profile page named "login_home_index_html", so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form.
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 1b0a0abf9a..edf4813d74 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -438,10 +438,12 @@ Now that we have used Rails scaffold generator for our `Post` resource, it has a
Let me take you through one such test, `test_should_get_index` from the file `posts_controller_test.rb`.
```ruby
-test "should get index" do
- get :index
- assert_response :success
- assert_not_nil assigns(:posts)
+class PostsControllerTest < ActionController::TestCase
+ test "should get index" do
+ get :index
+ assert_response :success
+ assert_not_nil assigns(:posts)
+ end
end
```
@@ -532,7 +534,7 @@ instance variable:
```ruby
# setting a HTTP Header
-@request.headers["Accepts"] = "text/plain, text/html"
+@request.headers["Accept"] = "text/plain, text/html"
get :index # simulate the request with custom header
# setting a CGI variable
@@ -757,24 +759,24 @@ class UserFlowsTest < ActionDispatch::IntegrationTest
private
- module CustomDsl
- def browses_site
- get "/products/all"
- assert_response :success
- assert assigns(:products)
+ module CustomDsl
+ def browses_site
+ get "/products/all"
+ assert_response :success
+ assert assigns(:products)
+ end
end
- end
- def login(user)
- open_session do |sess|
- sess.extend(CustomDsl)
- u = users(user)
- sess.https!
- sess.post "/login", username: u.username, password: u.password
- assert_equal '/welcome', path
- sess.https!(false)
+ def login(user)
+ open_session do |sess|
+ sess.extend(CustomDsl)
+ u = users(user)
+ sess.https!
+ sess.post "/login", username: u.username, password: u.password
+ assert_equal '/welcome', path
+ sess.https!(false)
+ end
end
- end
end
```
@@ -887,10 +889,9 @@ class PostsControllerTest < ActionController::TestCase
private
- def initialize_post
- @post = posts(:one)
- end
-
+ def initialize_post
+ @post = posts(:one)
+ end
end
```
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 357918a73c..224213268e 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -170,6 +170,7 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options.
```
* Rails 4.0 has deprecated `ActiveRecord::Fixtures` in favor of `ActiveRecord::FixtureSet`.
+
* Rails 4.0 has deprecated `ActiveRecord::TestCase` in favor of `ActiveSupport::TestCase`.
* Rails 4.0 has deprecated the old-style hash based finder API. This means that
@@ -338,7 +339,7 @@ config.assets.js_compressor = :uglifier
### sass-rails
-* `asset_url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`
+* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")`
Upgrading from Rails 3.1 to Rails 3.2
-------------------------------------
diff --git a/rails.gemspec b/rails.gemspec
index b426faf0e8..d1c199a97a 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'actionpack', version
s.add_dependency 'actionview', version
+ s.add_dependency 'activemodel', version
s.add_dependency 'activerecord', version
s.add_dependency 'actionmailer', version
s.add_dependency 'railties', version
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index cc9722e59c..8963f6e604 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,38 @@
+* Removal of all javascript stuff (gems and files) when generating a new
+ application using the `--skip-javascript` option.
+
+ *Robin Dupret*
+
+* Make the application name snake cased when it contains spaces
+
+ The application name is used to fill the `database.yml` and
+ `session_store.rb` files ; previously, if the provided name
+ contained whitespaces, it led to unexpected names in these files.
+
+ *Robin Dupret*
+
+* Added `--model-name` option to `ScaffoldControllerGenerator`.
+
+ *yalab*
+
+* Expose MiddlewareStack#unshift to environment configuration.
+
+ *Ben Pickles*
+
+* Include `web-console` into newly generated applications' Gemfile.
+
+ *Genadi Samokovarov*
+
+* `rails server` will only extend the logger to output to STDOUT
+ in development environment.
+
+ *Richard Schneeman*
+
+* Don't require passing path to app before options in `rails new`
+ and `rails plugin new`
+
+ *Piotr Sarnacki*
+
* rake notes now searches *.less files
*Josh Crowder*
@@ -5,21 +40,21 @@
* Generate nested route for namespaced controller generated using
`rails g controller`.
Fixes #11532.
-
+
Example:
-
+
rails g controller admin/dashboard index
-
+
# Before:
get "dashboard/index"
-
+
# After:
namespace :admin do
get "dashboard/index"
end
-
+
*Prathamesh Sonpatki*
-
+
* Fix the event name of action_dispatch requests.
*Rafael Mendonça França*
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index 1f9568fb5f..3e32576040 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -16,8 +16,7 @@ module Rails
:include => %w(
README.rdoc
lib/active_record/**/*.rb
- ),
- :exclude => 'lib/active_record/vendor/*'
+ )
},
'activemodel' => {
@@ -33,23 +32,22 @@ module Rails
lib/abstract_controller/**/*.rb
lib/action_controller/**/*.rb
lib/action_dispatch/**/*.rb
- ),
- :exclude => 'lib/action_controller/vendor/*'
+ )
},
'actionview' => {
:include => %w(
README.rdoc
lib/action_view/**/*.rb
- )
+ ),
+ :exclude => 'lib/action_view/vendor/*'
},
'actionmailer' => {
:include => %w(
README.rdoc
lib/action_mailer/**/*.rb
- ),
- :exclude => 'lib/action_mailer/vendor/*'
+ )
},
'railties' => {
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_rails_loader.rb
index 4a17803f1c..71fcf83dae 100644
--- a/railties/lib/rails/app_rails_loader.rb
+++ b/railties/lib/rails/app_rails_loader.rb
@@ -2,7 +2,7 @@ require 'pathname'
module Rails
module AppRailsLoader
- RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
+ RUBY = Gem.ruby
EXECUTABLES = ['bin/rails', 'script/rails']
BUNDLER_WARNING = <<EOS
Looks like your app's ./bin/rails is a stub that was generated by Bundler.
@@ -49,7 +49,7 @@ EOS
# call to generate a new application, so restore the original cwd.
Dir.chdir(original_cwd) and return if Pathname.new(Dir.pwd).root?
- # Otherwise keep moving upwards in search of a executable.
+ # Otherwise keep moving upwards in search of an executable.
Dir.chdir('..')
end
end
diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb
index 20313a2608..dd70c272c6 100644
--- a/railties/lib/rails/cli.rb
+++ b/railties/lib/rails/cli.rb
@@ -1,4 +1,3 @@
-require 'rbconfig'
require 'rails/app_rails_loader'
# If we are inside a Rails application this method performs an exec and thus
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 678697f09b..c998e6b6a8 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -13,5 +13,5 @@ module Rails
end
end
-Rails::Generators::AppPreparer.new(ARGV).prepare!
-Rails::Generators::AppGenerator.start
+args = Rails::Generators::ARGVScrubber.new(ARGV).prepare!
+Rails::Generators::AppGenerator.start args
diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb
index 11524c4ef5..59d2a0793e 100644
--- a/railties/lib/rails/commands/commands_tasks.rb
+++ b/railties/lib/rails/commands/commands_tasks.rb
@@ -51,6 +51,10 @@ EOT
generate_or_destroy(:generate)
end
+ def destroy
+ generate_or_destroy(:destroy)
+ end
+
def console
require_command!("console")
options = Rails::Console.parse_arguments(argv)
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 87d6505ed5..4201dac42f 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -1,6 +1,7 @@
require 'fileutils'
require 'optparse'
require 'action_dispatch'
+require 'rails'
module Rails
class Server < ::Rack::Server
@@ -32,7 +33,8 @@ module Rails
opt_parser.parse! args
- options[:server] = args.shift
+ options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development"
+ options[:server] = args.shift
options
end
end
@@ -74,7 +76,7 @@ module Rails
FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
end
- unless options[:daemonize]
+ if options[:log_stdout]
wrapped_app # touch the app so the logger is set up
console = ActiveSupport::Logger.new($stdout)
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index c694513960..f5d7dede66 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -59,6 +59,10 @@ module Rails
@operations << [__method__, args, block]
end
+ def unshift(*args, &block)
+ @operations << [__method__, args, block]
+ end
+
def merge_into(other) #:nodoc:
@operations.each do |operation, args, block|
other.send(operation, *args, &block)
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index f25f629aa5..e8adef2fd3 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -351,8 +351,13 @@ module Rails
Rails::Railtie::Configuration.eager_load_namespaces << base
base.called_from = begin
- # Remove the line number from backtraces making sure we don't leave anything behind
- call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
+ call_stack = if Kernel.respond_to?(:caller_locations)
+ caller_locations.map(&:path)
+ else
+ # Remove the line number from backtraces making sure we don't leave anything behind
+ caller.map { |p| p.sub(/:\d+.*/, '') }
+ end
+
File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] })
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index fb495c918c..07ec5e1873 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -5,6 +5,7 @@ require 'rails/version' unless defined?(Rails::VERSION)
require 'rbconfig'
require 'open-uri'
require 'uri'
+require 'rails/generators/base'
module Rails
module Generators
@@ -18,6 +19,10 @@ module Rails
argument :app_path, type: :string
+ def self.strict_args_position
+ false
+ end
+
def self.add_shared_options_for(name)
class_option :template, type: :string, aliases: '-m',
desc: "Path to some #{name} template (can be a filesystem path or URL)"
@@ -79,6 +84,17 @@ module Rails
protected
+ def gemfile_entries
+ @gemfile_entries ||= [
+ rails_gemfile_entry,
+ database_gemfile_entry,
+ assets_gemfile_entry,
+ javascript_gemfile_entry,
+ jbuilder_gemfile_entry,
+ webconsole_gemfile_entry,
+ sdoc_gemfile_entry].flatten
+ end
+
def builder
@builder ||= begin
builder_class = get_builder_class
@@ -92,11 +108,9 @@ module Rails
end
def create_root
- self.destination_root = File.expand_path(app_path, destination_root)
valid_const?
empty_directory '.'
- set_default_accessors!
FileUtils.cd(destination_root) unless options[:pretend]
end
@@ -107,6 +121,7 @@ module Rails
end
def set_default_accessors!
+ self.destination_root = File.expand_path(app_path, destination_root)
self.rails_template = case options[:template]
when /^https?:\/\//
options[:template]
@@ -118,11 +133,9 @@ module Rails
end
def database_gemfile_entry
- options[:skip_active_record] ? "" :
- <<-GEMFILE.strip_heredoc
- # Use #{options[:database]} as the database for Active Record
- gem '#{gem_for_database}'
- GEMFILE
+ return [] if options[:skip_active_record]
+ GemfileEntry.version gem_for_database, nil,
+ "Use #{options[:database]} as the database for Active Record"
end
def include_all_railties?
@@ -133,22 +146,39 @@ module Rails
options[value] ? '# ' : ''
end
+ class GemfileEntry < Struct.new(:name, :comment, :version, :options, :commented_out)
+ def initialize(name, comment, version, options = {}, commented_out = false)
+ super
+ end
+
+ def self.github(name, github, comment = nil)
+ new(name, comment, nil, github: github)
+ end
+
+ def self.version(name, version, comment = nil)
+ new(name, comment, version)
+ end
+
+ def self.path(name, path, comment = nil)
+ new(name, comment, nil, path: path)
+ end
+
+ def padding(max_width)
+ ' ' * (max_width - name.length + 2)
+ end
+ end
+
def rails_gemfile_entry
if options.dev?
- <<-GEMFILE.strip_heredoc
- gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}'
- gem 'arel', github: 'rails/arel'
- GEMFILE
+ [GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH),
+ GemfileEntry.github('arel', 'rails/arel')]
elsif options.edge?
- <<-GEMFILE.strip_heredoc
- gem 'rails', github: 'rails/rails'
- gem 'arel', github: 'rails/arel'
- GEMFILE
+ [GemfileEntry.github('rails', 'rails/rails'),
+ GemfileEntry.github('arel', 'rails/arel')]
else
- <<-GEMFILE.strip_heredoc
- # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
- gem 'rails', '#{Rails::VERSION::STRING}'
- GEMFILE
+ [GemfileEntry.version('rails',
+ Rails::VERSION::STRING,
+ "Bundle edge Rails instead: gem 'rails', github: 'rails/rails'")]
end
end
@@ -180,77 +210,72 @@ module Rails
end
def assets_gemfile_entry
- return if options[:skip_sprockets]
+ return [] if options[:skip_sprockets]
- gemfile = if options.dev? || options.edge?
- <<-GEMFILE.strip_heredoc
- # Use edge version of sprockets-rails
- gem 'sprockets-rails', github: 'rails/sprockets-rails'
-
- # Use SCSS for stylesheets
- gem 'sass-rails', github: 'rails/sass-rails'
- GEMFILE
+ gems = []
+ if options.dev? || options.edge?
+ gems << GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails',
+ 'Use edge version of sprockets-rails')
+ gems << GemfileEntry.github('sass-rails', 'rails/sass-rails',
+ 'Use SCSS for stylesheets')
else
- <<-GEMFILE.strip_heredoc
- # Use SCSS for stylesheets
- gem 'sass-rails', '~> 4.0.0.rc1'
- GEMFILE
+ gems << GemfileEntry.version('sass-rails',
+ '~> 4.0.0.rc1',
+ 'Use SCSS for stylesheets')
end
- gemfile += <<-GEMFILE.strip_heredoc
+ gems << GemfileEntry.version('uglifier',
+ '>= 1.3.0',
+ 'Use Uglifier as compressor for JavaScript assets')
- # Use Uglifier as compressor for JavaScript assets
- gem 'uglifier', '>= 1.3.0'
- GEMFILE
+ gems
+ end
- if options[:skip_javascript]
- gemfile += <<-GEMFILE
- #{coffee_gemfile_entry}
- #{javascript_runtime_gemfile_entry}
- GEMFILE
- end
+ def jbuilder_gemfile_entry
+ comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder'
+ GemfileEntry.version('jbuilder', '~> 1.2', comment)
+ end
+
+ def webconsole_gemfile_entry
+ comment = 'Run `rails console` in the browser. Read more: https://github.com/rails/web-console'
+ GemfileEntry.new('web-console', comment, nil, group: :development)
+ end
- gemfile.gsub(/^[ \t]+/, '')
+ def sdoc_gemfile_entry
+ comment = 'bundle exec rake doc:rails generates the API under doc/api.'
+ GemfileEntry.new('sdoc', comment, nil, { :group => :doc, :require => false })
end
def coffee_gemfile_entry
+ comment = 'Use CoffeeScript for .js.coffee assets and views'
if options.dev? || options.edge?
- <<-GEMFILE
- # Use CoffeeScript for .js.coffee assets and views
- gem 'coffee-rails', github: 'rails/coffee-rails'
- GEMFILE
+ GemfileEntry.github 'coffee-rails', 'rails/coffee-rails', comment
else
- <<-GEMFILE
- # Use CoffeeScript for .js.coffee assets and views
- gem 'coffee-rails', '~> 4.0.0'
- GEMFILE
+ GemfileEntry.version 'coffee-rails', '~> 4.0.0', comment
end
end
def javascript_gemfile_entry
- unless options[:skip_javascript]
- <<-GEMFILE.gsub(/^[ \t]+/, '')
- #{coffee_gemfile_entry}
- #{javascript_runtime_gemfile_entry}
- # Use #{options[:javascript]} as the JavaScript library
- gem '#{options[:javascript]}-rails'
-
- # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
- gem 'turbolinks'
- GEMFILE
+ if options[:skip_javascript]
+ []
+ else
+ gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry]
+ gems << GemfileEntry.version("#{options[:javascript]}-rails", nil,
+ "Use #{options[:javascript]} as the JavaScript library")
+
+ gems << GemfileEntry.version("turbolinks", nil,
+ "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks")
+ gems
end
end
def javascript_runtime_gemfile_entry
- runtime = if defined?(JRUBY_VERSION)
- "gem 'therubyrhino'"
+ comment = 'See https://github.com/sstephenson/execjs#readme for more supported runtimes'
+ if defined?(JRUBY_VERSION)
+ GemfileEntry.version 'therubyrhino', comment, nil
else
- "# gem 'therubyracer', platforms: :ruby"
+ GemfileEntry.new 'therubyracer', comment, nil, { :platforms => :ruby }, true
end
- <<-GEMFILE
- # See https://github.com/sstephenson/execjs#readme for more supported runtimes
- #{runtime}
- GEMFILE
end
def bundle_command(command)
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 8aec8bc8f9..681332a144 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -7,6 +7,7 @@ rescue LoadError
exit
end
+require 'rails/generators'
require 'rails/generators/actions'
module Rails
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 041bfcb733..04ef2ee071 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -129,7 +129,9 @@ module Rails
end
def vendor_javascripts
- empty_directory_with_keep_file 'vendor/assets/javascripts'
+ unless options[:skip_javascript]
+ empty_directory_with_keep_file 'vendor/assets/javascripts'
+ end
end
def vendor_stylesheets
@@ -151,23 +153,18 @@ module Rails
desc: "Show Rails version number and quit"
def initialize(*args)
- if args[0].blank?
- if args[1].blank?
- # rails new
- raise Error, "Application name should be provided in arguments. For details run: rails --help"
- else
- # rails new --skip-bundle my_new_application
- raise Error, "Options should be given after the application name. For details run: rails --help"
- end
- end
-
super
+ unless app_path
+ raise Error, "Application name should be provided in arguments. For details run: rails --help"
+ end
+
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
end
+ public_task :set_default_accessors!
public_task :create_root
def create_root_files
@@ -231,6 +228,12 @@ module Rails
build(:leftovers)
end
+ def delete_js_folder_skipping_javascript
+ if options[:skip_javascript]
+ remove_dir 'app/assets/javascripts'
+ end
+ end
+
public_task :apply_rails_template, :run_bundle
protected
@@ -245,7 +248,7 @@ module Rails
end
def app_name
- @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr(".", "_")
+ @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root)).tr('\\', '').tr(". ", "_")
end
def defined_app_name
@@ -308,58 +311,67 @@ module Rails
#
# This class should be called before the AppGenerator is required and started
# since it configures and mutates ARGV correctly.
- class AppPreparer # :nodoc
- attr_reader :argv
-
+ class ARGVScrubber # :nodoc
def initialize(argv = ARGV)
@argv = argv
end
def prepare!
- handle_version_request!(argv.first)
- unless handle_invalid_command!(argv.first)
- argv.shift
- handle_rails_rc!
+ handle_version_request!(@argv.first)
+ handle_invalid_command!(@argv.first, @argv) do
+ handle_rails_rc!(@argv.drop(1))
end
end
+ def self.default_rc_file
+ File.join(File.expand_path('~'), '.railsrc')
+ end
+
private
def handle_version_request!(argument)
- if ['--version', '-v'].include?(argv.first)
+ if ['--version', '-v'].include?(argument)
require 'rails/version'
puts "Rails #{Rails::VERSION::STRING}"
exit(0)
end
end
- def handle_invalid_command!(argument)
- if argument != "new"
- argv[0] = "--help"
+ def handle_invalid_command!(argument, argv)
+ if argument == "new"
+ yield
+ else
+ ['--help'] + argv.drop(1)
end
end
- def handle_rails_rc!
- unless argv.delete("--no-rc")
- insert_railsrc(railsrc)
+ def handle_rails_rc!(argv)
+ if argv.find { |arg| arg == '--no-rc' }
+ argv.reject { |arg| arg == '--no-rc' }
+ else
+ railsrc(argv) { |argv, rc| insert_railsrc_into_argv!(argv, rc) }
end
end
- def railsrc
+ def railsrc(argv)
if (customrc = argv.index{ |x| x.include?("--rc=") })
- File.expand_path(argv.delete_at(customrc).gsub(/--rc=/, ""))
+ fname = File.expand_path(argv[customrc].gsub(/--rc=/, ""))
+ yield(argv.take(customrc) + argv.drop(customrc + 1), fname)
else
- File.join(File.expand_path("~"), '.railsrc')
+ yield argv, self.class.default_rc_file
end
end
- def insert_railsrc(railsrc)
- if File.exist?(railsrc)
- extra_args_string = File.read(railsrc)
- extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten
- puts "Using #{extra_args.join(" ")} from #{railsrc}"
- argv.insert(1, *extra_args)
- end
+ def read_rc_file(railsrc)
+ extra_args = File.readlines(railsrc).flat_map(&:split)
+ puts "Using #{extra_args.join(" ")} from #{railsrc}"
+ extra_args
+ end
+
+ def insert_railsrc_into_argv!(argv, railsrc)
+ return argv unless File.exists?(railsrc)
+ extra_args = read_rc_file railsrc
+ argv.take(1) + extra_args + argv.drop(1)
end
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index adc83353b4..21dcba7947 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -1,28 +1,28 @@
source 'https://rubygems.org'
-<%= rails_gemfile_entry -%>
+<% max_width = gemfile_entries.map { |g| g.name.length }.max -%>
+<% gemfile_entries.each do |gem| -%>
+<% if gem.comment -%>
-<%= database_gemfile_entry -%>
-
-<%= assets_gemfile_entry %>
-<%= javascript_gemfile_entry -%>
-
-# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
-gem 'jbuilder', '~> 1.2'
-
-group :doc do
- # bundle exec rake doc:rails generates the API under doc/api.
- gem 'sdoc', require: false
-end
+# <%= gem.comment %>
+<% end -%>
+<%= gem.commented_out ? '# ' : '' %>gem '<%= gem.name %>'<% if gem.version -%>
+, '<%= gem.version %>'
+<% elsif gem.options.any? -%>
+,<%= gem.padding(max_width) %><%= gem.options.map { |k,v|
+ "#{k}: #{v.inspect}" }.join(', ') %>
+<% else %>
+<% end -%>
+<% end -%>
# Use ActiveModel has_secure_password
-# gem 'bcrypt-ruby', '~> 3.1.0'
+# gem 'bcrypt-ruby', '~> 3.1.2'
# Use unicorn as the app server
# gem 'unicorn'
# Use Capistrano for deployment
-# gem 'capistrano', group: :development
+# gem 'capistrano-rails', group: :development
<% unless defined?(JRUBY_VERSION) -%>
# Use debugger
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index c3d1578818..4cf47bd0a0 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -4,7 +4,6 @@
<title><%= camelized %></title>
<%- if options[:skip_javascript] -%>
<%%= stylesheet_link_tag "application", media: "all" %>
- <%%= javascript_include_tag "application" %>
<%- else -%>
<%%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%%= javascript_include_tag "application", "data-turbolinks-track" => true %>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index eb569b7dab..0194dce6f3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -55,6 +55,8 @@ production:
adapter: postgresql
encoding: unicode
database: <%= app_name %>_production
+ # For details on connection pooling, see rails configration guide
+ # http://guides.rubyonrails.org/configuring.html#database-pooling
pool: 5
username: <%= app_name %>
password:
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index 145d9ee6e0..833b7beb7f 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -60,7 +60,7 @@ Available field types:
For decimal, two integers separated by a comma in curly braces will be used
for precision and scale:
- `rails generate model product price:decimal{10,2}`
+ `rails generate model product 'price:decimal{10,2}'`
You can add a `:uniq` or `:index` suffix for unique or standard indexes
respectively:
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 13f5472ede..97ff6d1b8b 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -176,10 +176,12 @@ task default: :test
"skip adding entry to Gemfile"
def initialize(*args)
- raise Error, "Options should be given after the plugin name. For details run: rails plugin new --help" if args[0].blank?
-
@dummy_path = nil
super
+
+ unless plugin_path
+ raise Error, "Plugin name should be provided in arguments. For details run: rails plugin new --help"
+ end
end
public_task :create_root
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
index 3f2b78f2fd..d576784415 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile
@@ -23,7 +23,20 @@ end
<% if options.dev? || options.edge? -%>
# Your gem is dependent on dev or edge Rails. Once you can lock this
# dependency down to a specific version, move it to your gemspec.
-<%= rails_gemfile_entry -%>
+<% max_width = gemfile_entries.map { |g| g.name.length }.max -%>
+<% gemfile_entries.each do |gem| -%>
+<% if gem.comment -%>
+
+# <%= gem.comment %>
+<% end -%>
+<%= gem.commented_out ? '# ' : '' %>gem '<%= gem.name %>'<% if gem.version -%>
+, '<%= gem.version %>'
+<% elsif gem.options.any? -%>
+,<%= gem.padding(max_width) %><%= gem.options.map { |k,v|
+ "#{k}: #{v.inspect}" }.join(', ') %>
+<% else %>
+<% end -%>
+<% end -%>
<% end -%>
# To use debugger
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 4f36b612ae..6bf0a33a5f 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -13,7 +13,7 @@ module Rails
argument :attributes, type: :array, default: [], banner: "field:type field:type"
def create_controller_files
- template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb")
+ template "controller.rb", File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb")
end
hook_for :template_engine, :test_framework, as: :scaffold
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 7fd5c00768..a01eb57651 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -9,11 +9,19 @@ module Rails
def self.included(base) #:nodoc:
base.class_option :force_plural, type: :boolean, desc: "Forces the use of a plural ModelName"
+ base.class_option :model_name, type: :string, desc: "ModelName to be used"
end
# Set controller variables on initialization.
def initialize(*args) #:nodoc:
super
+ if options[:model_name]
+ controller_name = name
+ self.name = options[:model_name]
+ assign_names!(self.name)
+ else
+ controller_name = name
+ end
if name == name.pluralize && name.singularize != name.pluralize && !options[:force_plural]
unless ResourceHelpers.skip_warn
@@ -24,19 +32,26 @@ module Rails
assign_names!(name)
end
- @controller_name = name.pluralize
+ assign_controller_names!(controller_name.pluralize)
end
protected
- attr_reader :controller_name
+ attr_reader :controller_name, :controller_file_name
def controller_class_path
- class_path
+ if options[:model_name]
+ @controller_class_path
+ else
+ class_path
+ end
end
- def controller_file_name
- @controller_file_name ||= file_name.pluralize
+ def assign_controller_names!(name)
+ @controller_name = name
+ @controller_class_path = name.include?('/') ? name.split('/') : name.split('::')
+ @controller_class_path.map! { |m| m.underscore }
+ @controller_file_name = @controller_class_path.pop
end
def controller_file_path
diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb
index 6267b2f2ee..cc88e830dd 100644
--- a/railties/lib/rails/generators/testing/assertions.rb
+++ b/railties/lib/rails/generators/testing/assertions.rb
@@ -21,7 +21,7 @@ module Rails
# end
# end
def assert_file(relative, *contents)
- absolute = File.expand_path(relative, destination_root)
+ absolute = File.expand_path(relative, destination_root).shellescape
assert File.exists?(absolute), "Expected file #{relative.inspect} to exist, but does not"
read = File.read(absolute) if block_given? || !contents.empty?
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index de6795eda2..c8a74e794b 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -81,34 +81,28 @@ module Rails
end
def autoload_once
- filter_by(:autoload_once?)
+ filter_by { |p| p.autoload_once? }
end
def eager_load
- filter_by(:eager_load?)
+ filter_by { |p| p.eager_load? }
end
def autoload_paths
- filter_by(:autoload?)
+ filter_by { |p| p.autoload? }
end
def load_paths
- filter_by(:load_path?)
+ filter_by { |p| p.load_path? }
end
- protected
+ private
- def filter_by(constraint)
- all = []
- all_paths.each do |path|
- if path.send(constraint)
- paths = path.existent
- paths -= path.children.map { |p| p.send(constraint) ? [] : p.existent }.flatten
- all.concat(paths)
- end
- end
- all.uniq!
- all
+ def filter_by(&block)
+ all_paths.find_all(&block).flat_map { |path|
+ paths = path.existent
+ paths - path.children.map { |p| yield(p) ? [] : p.existent }.flatten
+ }.uniq
end
end
@@ -130,8 +124,9 @@ module Rails
end
def children
- keys = @root.keys.select { |k| k.include?(@current) }
- keys.delete(@current)
+ keys = @root.keys.find_all { |k|
+ k.start_with?(@current) && k != @current
+ }
@root.values_at(*keys.sort)
end
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index ef4cdcb080..3b35798679 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -11,7 +11,6 @@ module Rails
def initialize(app, taggers = nil)
@app = app
@taggers = taggers || []
- @instrumenter = ActiveSupport::Notifications.instrumenter
end
def call(env)
@@ -33,7 +32,8 @@ module Rails
logger.debug ''
end
- @instrumenter.start 'request.action_dispatch', request: request
+ instrumenter = ActiveSupport::Notifications.instrumenter
+ instrumenter.start 'request.action_dispatch', request: request
logger.info started_request_message(request)
resp = @app.call(env)
resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
@@ -70,7 +70,8 @@ module Rails
private
def finish(request)
- @instrumenter.finish 'request.action_dispatch', request: request
+ instrumenter = ActiveSupport::Notifications.instrumenter
+ instrumenter.finish 'request.action_dispatch', request: request
end
def development?
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 547846c833..38107e77b2 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -4,7 +4,7 @@ require 'rails/test_unit/sub_test_task'
task default: :test
-desc 'Runs test:units, test:functionals, test:integration together'
+desc 'Runs test:units, test:functionals, test:generators, test:integration together'
task :test do
Rails::TestTask.test_creator(Rake.application.top_level_tasks).invoke_rake_task
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 4de8fcaa38..035535ce22 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -293,7 +293,7 @@ module ApplicationTests
test "precompile should handle utf8 filenames" do
filename = "レイルズ.png"
- app_file "app/assets/images/#{filename}", "not a image really"
+ app_file "app/assets/images/#{filename}", "not an image really"
add_to_config "config.assets.precompile = [ /\.png$/, /application.(css|js)$/ ]"
precompile!
@@ -305,7 +305,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
get "/assets/#{URI.parser.escape(asset_path)}"
- assert_match "not a image really", last_response.body
+ assert_match "not an image really", last_response.body
assert_file_exists("#{app_path}/public/assets/#{asset_path}")
end
diff --git a/railties/test/application/basic_rendering_test.rb b/railties/test/application/basic_rendering_test.rb
deleted file mode 100644
index 00ba433a05..0000000000
--- a/railties/test/application/basic_rendering_test.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'isolation/abstract_unit'
-require 'rack/test'
-
-module ApplicationTests
- class BasicRenderingTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
- include Rack::Test::Methods
-
- def setup
- build_app
- end
-
- def teardown
- teardown_app
- end
-
- test "Rendering without ActionView" do
- gsub_app_file 'config/application.rb', "require 'rails/all'", <<-RUBY
- require "active_model/railtie"
- require "action_controller/railtie"
- RUBY
-
- # Turn off ActionView and jquery-rails (it depends on AV)
- $:.reject! {|path| path =~ /(actionview|jquery\-rails)/ }
- boot_rails
-
- app_file 'app/controllers/pages_controller.rb', <<-RUBY
- class PagesController < ApplicationController
- def render_hello_world
- render text: "Hello World!"
- end
-
- def render_nothing
- render nothing: true
- end
-
- def no_render; end
-
- def raise_error
- render foo: "bar"
- end
- end
- RUBY
-
- get '/pages/render_hello_world'
- assert_equal 200, last_response.status
- assert_equal "Hello World!", last_response.body
- assert_equal "text/plain; charset=utf-8", last_response.content_type
-
- get '/pages/render_nothing'
- assert_equal 200, last_response.status
- assert_equal " ", last_response.body
- assert_equal "text/plain; charset=utf-8", last_response.content_type
-
- get '/pages/no_render'
- assert_equal 500, last_response.status
-
- get '/pages/raise_error'
- assert_equal 500, last_response.status
- end
- end
-end
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index 91c5807379..946b82eeb3 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -33,6 +33,16 @@ module ApplicationTests
end
end
+ test "works with both headers individually" do
+ make_basic_app
+ assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do
+ assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1")
+ end
+ assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do
+ assert_equal "1.1.1.2", remote_ip("HTTP_CLIENT_IP" => "1.1.1.2")
+ end
+ end
+
test "can disable IP spoofing check" do
make_basic_app do |app|
app.config.action_dispatch.ip_spoofing_check = false
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 31a35a09bb..20d1d76d78 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -144,6 +144,12 @@ module ApplicationTests
assert_equal "Rack::Config", middleware.second
end
+ test 'unshift middleware' do
+ add_to_config 'config.middleware.unshift Rack::Config'
+ boot!
+ assert_equal 'Rack::Config', middleware.first
+ end
+
test "Rails.cache does not respond to middleware" do
add_to_config "config.cache_store = :memory_store"
boot!
diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb
index 03c343c475..5bfea599e0 100644
--- a/railties/test/application/multiple_applications_test.rb
+++ b/railties/test/application/multiple_applications_test.rb
@@ -110,7 +110,7 @@ module ApplicationTests
assert_equal 0, $run_count, "Without loading the initializers, the count should be 0"
- # Set config.eager_load to false so that a eager_load warning doesn't pop up
+ # 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"
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index cb57b3c0cd..ba688f1e9e 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -27,16 +27,62 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
def test_environment_with_rails_env
- with_rails_env 'production' do
- server = Rails::Server.new
- assert_equal 'production', server.options[:environment]
+ with_rack_env nil do
+ with_rails_env 'production' do
+ server = Rails::Server.new
+ assert_equal 'production', server.options[:environment]
+ end
end
end
def test_environment_with_rack_env
- with_rack_env 'production' do
- server = Rails::Server.new
- assert_equal 'production', server.options[:environment]
+ with_rails_env nil do
+ with_rack_env 'production' do
+ server = Rails::Server.new
+ assert_equal 'production', server.options[:environment]
+ end
+ end
+ end
+
+ def test_log_stdout
+ with_rack_env nil do
+ with_rails_env nil do
+ args = []
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal true, options[:log_stdout]
+
+ args = ["-e", "development"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal true, options[:log_stdout]
+
+ args = ["-e", "production"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal false, options[:log_stdout]
+
+ with_rack_env 'development' do
+ args = []
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal true, options[:log_stdout]
+ end
+
+ with_rack_env 'production' do
+ args = []
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal false, options[:log_stdout]
+ end
+
+ with_rails_env 'development' do
+ args = []
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal true, options[:log_stdout]
+ end
+
+ with_rails_env 'production' do
+ args = []
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal false, options[:log_stdout]
+ end
+ end
end
end
end
diff --git a/railties/test/env_helpers.rb b/railties/test/env_helpers.rb
index 6223c85bbf..330fe150ca 100644
--- a/railties/test/env_helpers.rb
+++ b/railties/test/env_helpers.rb
@@ -1,7 +1,10 @@
+require 'rails'
+
module EnvHelpers
private
def with_rails_env(env)
+ Rails.instance_variable_set :@_env, nil
switch_env 'RAILS_ENV', env do
switch_env 'RACK_ENV', nil do
yield
@@ -10,6 +13,7 @@ module EnvHelpers
end
def with_rack_env(env)
+ Rails.instance_variable_set :@_env, nil
switch_env 'RACK_ENV', env do
switch_env 'RAILS_ENV', nil do
yield
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 2f0dfc7d3e..39d4acb7b2 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -4,6 +4,7 @@ require 'generators/shared_generator_tests'
DEFAULT_APP_FILES = %w(
.gitignore
+ README.rdoc
Gemfile
Rakefile
config.ru
@@ -254,7 +255,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_gem "therubyrhino"
else
- assert_file "Gemfile", /# gem\s+["']therubyracer["']+, platforms: :ruby$/
+ assert_file "Gemfile", /# gem\s+["']therubyracer["']+, \s+platforms: :ruby$/
end
end
@@ -298,18 +299,30 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_javascript_is_skipped_if_required
run_generator [destination_root, "--skip-javascript"]
- assert_file "app/assets/javascripts/application.js" do |contents|
- assert_no_match %r{^//=\s+require\s}, contents
- end
+
+ assert_no_file "app/assets/javascripts"
+ assert_no_file "vendor/assets/javascripts"
+
assert_file "app/views/layouts/application.html.erb" do |contents|
assert_match(/stylesheet_link_tag\s+"application", media: "all" %>/, contents)
- assert_match(/javascript_include_tag\s+"application" \%>/, contents)
+ assert_no_match(/javascript_include_tag\s+"application" \%>/, contents)
end
+
assert_file "Gemfile" do |content|
- assert_match(/coffee-rails/, content)
+ assert_no_match(/coffee-rails/, content)
end
end
+ def test_inclusion_of_web_console
+ run_generator
+ assert_file "Gemfile", /gem 'web-console', \s+group: :development/
+ end
+
+ def test_inclusion_of_jbuilder
+ run_generator
+ assert_file "Gemfile", /gem 'jbuilder'/
+ end
+
def test_inclusion_of_debugger
run_generator
assert_file "Gemfile", /# gem 'debugger'/
@@ -317,7 +330,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_inclusion_of_lazy_loaded_sdoc
run_generator
- assert_file 'Gemfile', /gem 'sdoc', require: false/
+ assert_file 'Gemfile', /gem 'sdoc', \s+group: :doc, require: false/
end
def test_template_from_dir_pwd
@@ -367,6 +380,16 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/run bundle install/, output)
end
+ def test_application_name_with_spaces
+ path = File.join(destination_root, "foo bar".shellescape)
+
+ # This also applies to MySQL apps but not with SQLite
+ run_generator [path, "-d", 'postgresql']
+
+ assert_file "foo bar/config/database.yml", /database: foo_bar_development/
+ assert_file "foo bar/config/initializers/session_store.rb", /key: '_foo_bar/
+ end
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb
new file mode 100644
index 0000000000..a94350cbd7
--- /dev/null
+++ b/railties/test/generators/argv_scrubber_test.rb
@@ -0,0 +1,136 @@
+require 'active_support/test_case'
+require 'active_support/testing/autorun'
+require 'rails/generators/rails/app/app_generator'
+require 'tempfile'
+
+module Rails
+ module Generators
+ class ARGVScrubberTest < ActiveSupport::TestCase # :nodoc:
+ # Future people who read this... These tests are just to surround the
+ # current behavior of the ARGVScrubber, they do not mean that the class
+ # *must* act this way, I just want to prevent regressions.
+
+ def test_version
+ ['-v', '--version'].each do |str|
+ scrubber = ARGVScrubber.new [str]
+ output = nil
+ exit_code = nil
+ scrubber.extend(Module.new {
+ define_method(:puts) { |str| output = str }
+ define_method(:exit) { |code| exit_code = code }
+ })
+ scrubber.prepare!
+ assert_equal "Rails #{Rails::VERSION::STRING}", output
+ assert_equal 0, exit_code
+ end
+ end
+
+ def test_default_help
+ argv = ['zomg', 'how', 'are', 'you']
+ scrubber = ARGVScrubber.new argv
+ args = scrubber.prepare!
+ assert_equal ['--help'] + argv.drop(1), args
+ end
+
+ def test_prepare_returns_args
+ scrubber = ARGVScrubber.new ['hi mom']
+ args = scrubber.prepare!
+ assert_equal '--help', args.first
+ end
+
+ def test_no_mutations
+ scrubber = ARGVScrubber.new ['hi mom'].freeze
+ args = scrubber.prepare!
+ assert_equal '--help', args.first
+ end
+
+ def test_new_command_no_rc
+ scrubber = Class.new(ARGVScrubber) {
+ def self.default_rc_file
+ File.join(Dir.tmpdir, 'whatever')
+ end
+ }.new ['new']
+ args = scrubber.prepare!
+ assert_equal [], args
+ end
+
+ def test_new_homedir_rc
+ file = Tempfile.new 'myrcfile'
+ file.puts '--hello-world'
+ file.flush
+
+ message = nil
+ scrubber = Class.new(ARGVScrubber) {
+ define_singleton_method(:default_rc_file) do
+ file.path
+ end
+ define_method(:puts) { |msg| message = msg }
+ }.new ['new']
+ args = scrubber.prepare!
+ assert_equal ['--hello-world'], args
+ assert_match 'hello-world', message
+ assert_match file.path, message
+ ensure
+ file.close
+ file.unlink
+ end
+
+ def test_rc_whitespace_separated
+ file = Tempfile.new 'myrcfile'
+ file.puts '--hello --world'
+ file.flush
+
+ message = nil
+ scrubber = Class.new(ARGVScrubber) {
+ define_method(:puts) { |msg| message = msg }
+ }.new ['new', "--rc=#{file.path}"]
+ args = scrubber.prepare!
+ assert_equal ['--hello', '--world'], args
+ ensure
+ file.close
+ file.unlink
+ end
+
+ def test_new_rc_option
+ file = Tempfile.new 'myrcfile'
+ file.puts '--hello-world'
+ file.flush
+
+ message = nil
+ scrubber = Class.new(ARGVScrubber) {
+ define_method(:puts) { |msg| message = msg }
+ }.new ['new', "--rc=#{file.path}"]
+ args = scrubber.prepare!
+ assert_equal ['--hello-world'], args
+ assert_match 'hello-world', message
+ assert_match file.path, message
+ ensure
+ file.close
+ file.unlink
+ end
+
+ def test_new_rc_option_and_custom_options
+ file = Tempfile.new 'myrcfile'
+ file.puts '--hello'
+ file.puts '--world'
+ file.flush
+
+ scrubber = Class.new(ARGVScrubber) {
+ define_method(:puts) { |msg| }
+ }.new ['new', 'tenderapp', '--love', "--rc=#{file.path}"]
+
+ args = scrubber.prepare!
+ assert_equal ["tenderapp", "--hello", "--world", "--love"], args
+ ensure
+ file.close
+ file.unlink
+ end
+
+ def test_no_rc
+ scrubber = ARGVScrubber.new ['new', '--no-rc']
+ args = scrubber.prepare!
+ assert_equal [], args
+ end
+ end
+ end
+end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index 2bc2c33a72..ac5cfff229 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -117,6 +117,25 @@ class NamedBaseTest < Rails::Generators::TestCase
assert Rails::Generators.hidden_namespaces.include?('hidden')
end
+ def test_scaffold_plural_names_with_model_name_option
+ g = generator ['Admin::Foo'], model_name: 'User'
+ assert_name g, 'user', :singular_name
+ assert_name g, 'User', :name
+ assert_name g, 'user', :file_path
+ assert_name g, 'User', :class_name
+ assert_name g, 'user', :file_name
+ assert_name g, 'User', :human_name
+ assert_name g, 'users', :plural_name
+ assert_name g, 'user', :i18n_scope
+ assert_name g, 'users', :table_name
+ assert_name g, 'Admin::Foos', :controller_name
+ assert_name g, %w(admin), :controller_class_path
+ assert_name g, 'Admin::Foos', :controller_class_name
+ assert_name g, 'admin/foos', :controller_file_path
+ assert_name g, 'foos', :controller_file_name
+ assert_name g, 'admin.foos', :controller_i18n_scope
+ end
+
protected
def assert_name(generator, value, method)
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 013cb78252..26e56a162c 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -166,4 +166,13 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_match(/render action: 'new'/, content)
end
end
+
+ def test_model_name_option
+ run_generator ["Admin::User", "--model-name=User"]
+ assert_file "app/controllers/admin/users_controller.rb" do |content|
+ assert_instance_method :index, content do |m|
+ assert_match("@users = User.all", m)
+ end
+ end
+ end
end
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 369a0ee46c..7184639d23 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -46,11 +46,6 @@ module SharedGeneratorTests
assert_no_file "test"
end
- def test_options_before_application_name_raises_an_error
- content = capture(:stderr){ run_generator(["--pretend", destination_root]) }
- assert_match(/Options should be given after the \w+ name. For details run: rails( plugin new)? --help\n/, content)
- end
-
def test_name_collision_raises_an_error
reserved_words = %w[application destroy plugin runner test]
reserved_words.each do |reserved|
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 12f18b9dbf..178c505865 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -180,7 +180,7 @@ class PathsTest < ActiveSupport::TestCase
assert_equal 1, @root.eager_load.select {|p| p == @root["app"].expanded.first }.size
end
- test "paths added to a eager_load path should be added to the eager_load collection" do
+ test "paths added to an eager_load path should be added to the eager_load collection" do
@root["app"] = "/app"
@root["app"].eager_load!
@root["app"] << "/app2"