aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile6
-rw-r--r--RELEASING_RAILS.rdoc2
-rw-r--r--actionmailer/CHANGELOG.md14
-rw-r--r--actionmailer/actionmailer.gemspec1
-rw-r--r--actionmailer/lib/action_mailer/base.rb22
-rw-r--r--actionmailer/test/base_test.rb7
-rw-r--r--actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb0
-rw-r--r--actionmailer/test/fixtures/proc_mailer/welcome.html.erb0
-rw-r--r--actionpack/CHANGELOG.md91
-rw-r--r--actionpack/actionpack.gemspec3
-rw-r--r--actionpack/lib/abstract_controller/base.rb16
-rw-r--r--actionpack/lib/abstract_controller/collector.rb6
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb15
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb3
-rw-r--r--actionpack/lib/action_controller/caching.rb3
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb2
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb22
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb20
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb14
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb42
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb45
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing.rb7
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb10
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
-rw-r--r--actionpack/lib/action_view.rb4
-rw-r--r--actionpack/lib/action_view/asset_paths.rb8
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb4
-rw-r--r--actionpack/lib/action_view/helpers/controller_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb94
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb52
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb214
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb5
-rw-r--r--actionpack/lib/action_view/helpers/tags/color_field.rb25
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_field.rb22
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_local_field.rb19
-rw-r--r--actionpack/lib/action_view/helpers/tags/month_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/week_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb34
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb51
-rw-r--r--actionpack/lib/action_view/locale/en.yml98
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb2
-rw-r--r--actionpack/lib/action_view/template/resolver.rb13
-rw-r--r--actionpack/lib/action_view/test_case.rb3
-rw-r--r--actionpack/test/abstract/helper_test.rb7
-rw-r--r--actionpack/test/abstract_unit.rb1
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb28
-rw-r--r--actionpack/test/controller/force_ssl_test.rb31
-rw-r--r--actionpack/test/controller/helper_test.rb62
-rw-r--r--actionpack/test/controller/integration_test.rb19
-rw-r--r--actionpack/test/controller/render_test.rb29
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb4
-rw-r--r--actionpack/test/controller/routing_test.rb15
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb25
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb10
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb11
-rw-r--r--actionpack/test/dispatch/request/session_test.rb16
-rw-r--r--actionpack/test/dispatch/routing_test.rb6
-rw-r--r--actionpack/test/dispatch/test_request_test.rb7
-rw-r--r--actionpack/test/fixtures/helpers1_pack/pack1_helper.rb5
-rw-r--r--actionpack/test/fixtures/helpers2_pack/pack2_helper.rb5
-rw-r--r--actionpack/test/fixtures/public/foo/baz.css3
-rw-r--r--actionpack/test/lib/controller/fake_models.rb3
-rw-r--r--actionpack/test/metal/caching_test.rb32
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb14
-rw-r--r--actionpack/test/template/form_helper_test.rb179
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb25
-rw-r--r--actionpack/test/template/number_helper_test.rb11
-rw-r--r--actionpack/test/template/test_case_test.rb19
-rw-r--r--actionpack/test/template/testing/null_resolver_test.rb2
-rw-r--r--actionpack/test/template/text_helper_test.rb67
-rw-r--r--actionpack/test/template/translation_helper_test.rb16
-rw-r--r--actionpack/test/template/url_helper_test.rb27
-rw-r--r--activemodel/CHANGELOG.md49
-rw-r--r--activemodel/activemodel.gemspec1
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb13
-rw-r--r--activemodel/lib/active_model/configuration.rb134
-rw-r--r--activemodel/lib/active_model/dirty.rb59
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb14
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb2
-rw-r--r--activemodel/lib/active_model/observer_array.rb16
-rw-r--r--activemodel/lib/active_model/serializers/json.rb89
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb2
-rw-r--r--activemodel/lib/active_model/translation.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb3
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb3
-rw-r--r--activemodel/lib/active_model/validations/format.rb23
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb4
-rw-r--r--activemodel/lib/active_model/validations/validates.rb1
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb22
-rw-r--r--activemodel/test/cases/configuration_test.rb154
-rw-r--r--activemodel/test/cases/mass_assignment_security/permission_set_test.rb6
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb90
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb2
-rw-r--r--activemodel/test/cases/translation_test.rb5
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb18
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations_test.rb5
-rw-r--r--activerecord/CHANGELOG.md106
-rw-r--r--activerecord/README.rdoc20
-rw-r--r--activerecord/Rakefile10
-rw-r--r--activerecord/activerecord.gemspec1
-rw-r--r--activerecord/lib/active_record.rb12
-rw-r--r--activerecord/lib/active_record/aggregations.rb262
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb19
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb724
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb29
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb30
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb29
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb9
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb16
-rw-r--r--activerecord/lib/active_record/callbacks.rb24
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb252
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb50
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_handling.rb4
-rw-r--r--activerecord/lib/active_record/core.rb157
-rw-r--r--activerecord/lib/active_record/counter_cache.rb200
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb4
-rw-r--r--activerecord/lib/active_record/explain.rb19
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/fixtures/file.rb44
-rw-r--r--activerecord/lib/active_record/inheritance.rb10
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb10
-rw-r--r--activerecord/lib/active_record/migration.rb37
-rw-r--r--activerecord/lib/active_record/model.rb103
-rw-r--r--activerecord/lib/active_record/model_schema.rb23
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb6
-rw-r--r--activerecord/lib/active_record/null_relation.rb17
-rw-r--r--activerecord/lib/active_record/persistence.rb38
-rw-r--r--activerecord/lib/active_record/railtie.rb39
-rw-r--r--activerecord/lib/active_record/railties/databases.rake243
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb48
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/batches.rb27
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb50
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb10
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb29
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb97
-rw-r--r--activerecord/lib/active_record/sanitization.rb32
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb14
-rw-r--r--activerecord/lib/active_record/scoping/default.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb12
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb4
-rw-r--r--activerecord/lib/active_record/store.rb87
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb109
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb110
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb81
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb55
-rw-r--r--activerecord/lib/active_record/test_case.rb50
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
-rw-r--r--activerecord/lib/active_record/transactions.rb4
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb48
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb15
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb36
-rw-r--r--activerecord/test/cases/aggregations_test.rb158
-rw-r--r--activerecord/test/cases/associations/eager_test.rb9
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb22
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb22
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb1
-rw-r--r--activerecord/test/cases/base_test.rb57
-rw-r--r--activerecord/test/cases/calculations_test.rb62
-rw-r--r--activerecord/test/cases/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb5
-rw-r--r--activerecord/test/cases/connection_pool_test.rb21
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb3
-rw-r--r--activerecord/test/cases/database_tasks_test.rb275
-rw-r--r--activerecord/test/cases/deprecated_dynamic_methods_test.rb76
-rw-r--r--activerecord/test/cases/dirty_test.rb21
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb10
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb3
-rw-r--r--activerecord/test/cases/finder_test.rb114
-rw-r--r--activerecord/test/cases/helper.rb16
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb87
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb2
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb3
-rw-r--r--activerecord/test/cases/migration_test.rb17
-rw-r--r--activerecord/test/cases/mysql_rake_test.rb246
-rw-r--r--activerecord/test/cases/named_scope_test.rb10
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb10
-rw-r--r--activerecord/test/cases/persistence_test.rb70
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb4
-rw-r--r--activerecord/test/cases/postgresql_rake_test.rb200
-rw-r--r--activerecord/test/cases/query_cache_test.rb7
-rw-r--r--activerecord/test/cases/reaper_test.rb2
-rw-r--r--activerecord/test/cases/reflection_test.rb24
-rw-r--r--activerecord/test/cases/relation/where_test.rb19
-rw-r--r--activerecord/test/cases/relations_test.rb15
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb39
-rw-r--r--activerecord/test/cases/sqlite_rake_test.rb170
-rw-r--r--activerecord/test/cases/store_test.rb45
-rw-r--r--activerecord/test/cases/timestamp_test.rb4
-rw-r--r--activerecord/test/cases/transactions_test.rb10
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb14
-rw-r--r--activerecord/test/fixtures/admin/users.yml3
-rw-r--r--activerecord/test/models/customer.rb8
-rw-r--r--activerecord/test/models/developer.rb5
-rw-r--r--activerecord/test/models/person.rb21
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb10
-rw-r--r--activesupport/CHANGELOG.md40
-rw-r--r--activesupport/activesupport.gemspec3
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/cache.rb2
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb3
-rw-r--r--activesupport/lib/active_support/callbacks.rb19
-rw-r--r--activesupport/lib/active_support/configurable.rb57
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb65
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb118
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb135
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb11
-rw-r--r--activesupport/lib/active_support/dependencies.rb23
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb1
-rw-r--r--activesupport/lib/active_support/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb19
-rw-r--r--activesupport/lib/active_support/gzip.rb9
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb7
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb23
-rw-r--r--activesupport/lib/active_support/json/decoding.rb13
-rw-r--r--activesupport/lib/active_support/json/encoding.rb24
-rw-r--r--activesupport/lib/active_support/json/variable.rb9
-rw-r--r--activesupport/lib/active_support/locale/en.yml99
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb60
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb4
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/notifications.rb11
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb31
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb18
-rw-r--r--activesupport/lib/active_support/number_helper.rb532
-rw-r--r--activesupport/lib/active_support/test_case.rb3
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb39
-rw-r--r--activesupport/lib/active_support/testing/performance.rb49
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby.rb5
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb12
-rw-r--r--activesupport/lib/active_support/xml_mini.rb11
-rw-r--r--activesupport/test/caching_test.rb9
-rw-r--r--activesupport/test/clean_backtrace_test.rb11
-rw-r--r--activesupport/test/configurable_test.rb22
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb5
-rw-r--r--activesupport/test/core_ext/class/attribute_test.rb7
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb5
-rw-r--r--activesupport/test/core_ext/file_test.rb6
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb212
-rw-r--r--activesupport/test/core_ext/kernel_test.rb4
-rw-r--r--activesupport/test/core_ext/module_test.rb2
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb261
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb13
-rw-r--r--activesupport/test/dependencies_test.rb17
-rw-r--r--activesupport/test/inflector_test.rb10
-rw-r--r--activesupport/test/inflector_test_cases.rb2
-rw-r--r--activesupport/test/json/encoding_test.rb8
-rw-r--r--activesupport/test/notifications_test.rb4
-rw-r--r--activesupport/test/number_helper_test.rb375
-rw-r--r--activesupport/test/test_case_test.rb58
-rw-r--r--activesupport/test/test_test.rb82
-rw-r--r--activesupport/test/testing/performance_test.rb59
-rw-r--r--guides/Rakefile78
-rw-r--r--guides/assets/images/belongs_to.pngbin34017 -> 26076 bytes
-rw-r--r--guides/assets/images/book_icon.gifbin337 -> 329 bytes
-rw-r--r--guides/assets/images/challenge.pngbin54134 -> 33373 bytes
-rw-r--r--guides/assets/images/chapters_icon.gifbin628 -> 620 bytes
-rw-r--r--guides/assets/images/check_bullet.gifbin384 -> 376 bytes
-rw-r--r--guides/assets/images/credits_pic_blank.gifbin613 -> 597 bytes
-rw-r--r--guides/assets/images/csrf.pngbin41996 -> 32179 bytes
-rw-r--r--guides/assets/images/customized_error_messages.pngbin5055 -> 2561 bytes
-rw-r--r--guides/assets/images/edge_badge.pngbin7945 -> 5964 bytes
-rw-r--r--guides/assets/images/error_messages.pngbin14645 -> 10964 bytes
-rw-r--r--guides/assets/images/getting_started/confirm_dialog.pngbin36070 -> 29542 bytes
-rw-r--r--guides/assets/images/getting_started/form_with_errors.pngbin20820 -> 14031 bytes
-rw-r--r--guides/assets/images/getting_started/index_action_with_edit_link.pngbin15547 -> 9772 bytes
-rw-r--r--guides/assets/images/getting_started/new_post.pngbin14334 -> 5888 bytes
-rw-r--r--guides/assets/images/getting_started/post_with_comments.pngbin31630 -> 18496 bytes
-rw-r--r--guides/assets/images/getting_started/routing_error_no_controller.pngbin15744 -> 6268 bytes
-rw-r--r--guides/assets/images/getting_started/routing_error_no_route_matches.pngbin16065 -> 6508 bytes
-rw-r--r--guides/assets/images/getting_started/show_action_for_posts.pngbin6885 -> 2991 bytes
-rw-r--r--guides/assets/images/getting_started/template_is_missing_posts_new.pngbin15168 -> 5851 bytes
-rw-r--r--guides/assets/images/getting_started/undefined_method_post_path.pngbin15254 -> 9217 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_create_for_posts.pngbin12652 -> 4146 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_new_for_posts.pngbin12756 -> 4208 bytes
-rw-r--r--guides/assets/images/grey_bullet.gifbin45 -> 37 bytes
-rw-r--r--guides/assets/images/habtm.pngbin63801 -> 49332 bytes
-rw-r--r--guides/assets/images/has_many.pngbin38582 -> 28988 bytes
-rw-r--r--guides/assets/images/has_many_through.pngbin100220 -> 79428 bytes
-rw-r--r--guides/assets/images/has_one.pngbin39022 -> 29072 bytes
-rw-r--r--guides/assets/images/has_one_through.pngbin92594 -> 72434 bytes
-rw-r--r--guides/assets/images/header_backdrop.pngbin882 -> 224 bytes
-rw-r--r--guides/assets/images/i18n/demo_html_safe.pngbin11946 -> 10073 bytes
-rw-r--r--guides/assets/images/i18n/demo_localized_pirate.pngbin15027 -> 11485 bytes
-rw-r--r--guides/assets/images/i18n/demo_translated_en.pngbin12057 -> 9325 bytes
-rw-r--r--guides/assets/images/i18n/demo_translated_pirate.pngbin13392 -> 10202 bytes
-rw-r--r--guides/assets/images/i18n/demo_translation_missing.pngbin13143 -> 10260 bytes
-rw-r--r--guides/assets/images/i18n/demo_untranslated.pngbin11925 -> 9224 bytes
-rw-r--r--guides/assets/images/icons/callouts/1.pngbin329 -> 147 bytes
-rw-r--r--guides/assets/images/icons/callouts/10.pngbin361 -> 183 bytes
-rw-r--r--guides/assets/images/icons/callouts/11.pngbin565 -> 290 bytes
-rw-r--r--guides/assets/images/icons/callouts/12.pngbin617 -> 322 bytes
-rw-r--r--guides/assets/images/icons/callouts/13.pngbin623 -> 328 bytes
-rw-r--r--guides/assets/images/icons/callouts/14.pngbin411 -> 246 bytes
-rw-r--r--guides/assets/images/icons/callouts/15.pngbin640 -> 340 bytes
-rw-r--r--guides/assets/images/icons/callouts/2.pngbin353 -> 168 bytes
-rw-r--r--guides/assets/images/icons/callouts/3.pngbin350 -> 170 bytes
-rw-r--r--guides/assets/images/icons/callouts/4.pngbin345 -> 165 bytes
-rw-r--r--guides/assets/images/icons/callouts/5.pngbin348 -> 169 bytes
-rw-r--r--guides/assets/images/icons/callouts/6.pngbin355 -> 176 bytes
-rw-r--r--guides/assets/images/icons/callouts/7.pngbin344 -> 160 bytes
-rw-r--r--guides/assets/images/icons/callouts/8.pngbin357 -> 176 bytes
-rw-r--r--guides/assets/images/icons/callouts/9.pngbin357 -> 177 bytes
-rw-r--r--guides/assets/images/icons/caution.pngbin2554 -> 2300 bytes
-rw-r--r--guides/assets/images/icons/example.pngbin2354 -> 2079 bytes
-rw-r--r--guides/assets/images/icons/home.pngbin1340 -> 1163 bytes
-rw-r--r--guides/assets/images/icons/important.pngbin2657 -> 2451 bytes
-rw-r--r--guides/assets/images/icons/next.pngbin1302 -> 1146 bytes
-rw-r--r--guides/assets/images/icons/note.pngbin2730 -> 2155 bytes
-rw-r--r--guides/assets/images/icons/prev.pngbin1348 -> 1126 bytes
-rw-r--r--guides/assets/images/icons/tip.pngbin2602 -> 2248 bytes
-rw-r--r--guides/assets/images/icons/up.pngbin1320 -> 1133 bytes
-rw-r--r--guides/assets/images/icons/warning.pngbin2828 -> 2616 bytes
-rw-r--r--guides/assets/images/nav_arrow.gifbin427 -> 419 bytes
-rw-r--r--guides/assets/images/oscardelben.jpgbin0 -> 6299 bytes
-rw-r--r--guides/assets/images/polymorphic.pngbin85248 -> 66415 bytes
-rw-r--r--guides/assets/images/rails_guides_logo.gifbin5114 -> 5106 bytes
-rw-r--r--guides/assets/images/rails_welcome.pngbin121314 -> 71979 bytes
-rw-r--r--guides/assets/images/session_fixation.pngbin47860 -> 38451 bytes
-rw-r--r--guides/assets/images/tab_grey.gifbin4924 -> 4684 bytes
-rw-r--r--guides/assets/images/tab_info.gifbin4762 -> 4522 bytes
-rw-r--r--guides/assets/images/tab_note.gifbin4807 -> 4566 bytes
-rw-r--r--guides/assets/images/tab_red.gifbin4753 -> 4507 bytes
-rw-r--r--guides/assets/images/tab_yellow.gifbin4759 -> 4519 bytes
-rw-r--r--guides/assets/images/tab_yellow.pngbin1611 -> 1441 bytes
-rw-r--r--guides/assets/images/validation_error_messages.pngbin1107 -> 583 bytes
-rw-r--r--guides/code/getting_started/app/controllers/welcome_controller.rb (renamed from guides/code/getting_started/app/controllers/home_controller.rb)1
-rw-r--r--guides/code/getting_started/test/functional/welcome_controller_test.rb (renamed from guides/code/getting_started/test/functional/home_controller_test.rb)1
-rw-r--r--guides/rails_guides/generator.rb9
-rw-r--r--guides/source/4_0_release_notes.textile591
-rw-r--r--guides/source/action_view_overview.textile66
-rw-r--r--guides/source/active_model_basics.textile2
-rw-r--r--guides/source/active_record_querying.textile48
-rw-r--r--guides/source/active_support_core_extensions.textile188
-rw-r--r--guides/source/active_support_instrumentation.textile4
-rw-r--r--guides/source/caching_with_rails.textile2
-rw-r--r--guides/source/command_line.textile198
-rw-r--r--guides/source/configuring.textile2
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile71
-rw-r--r--guides/source/credits.html.erb4
-rw-r--r--guides/source/debugging_rails_applications.textile2
-rw-r--r--guides/source/engines.textile6
-rw-r--r--guides/source/form_helpers.textile26
-rw-r--r--guides/source/getting_started.textile56
-rw-r--r--guides/source/initialization.textile863
-rw-r--r--guides/source/layouts_and_rendering.textile2
-rw-r--r--guides/source/migrations.textile3
-rw-r--r--guides/source/rails_on_rack.textile79
-rw-r--r--guides/source/routing.textile24
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.textile24
-rw-r--r--guides/source/security.textile112
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile22
-rw-r--r--rails.gemspec1
-rw-r--r--railties/CHANGELOG.md6
-rw-r--r--railties/lib/rails/application.rb23
-rw-r--r--railties/lib/rails/application/finisher.rb2
-rw-r--r--railties/lib/rails/application/route_inspector.rb2
-rw-r--r--railties/lib/rails/code_statistics.rb11
-rw-r--r--railties/lib/rails/commands.rb10
-rw-r--r--railties/lib/rails/commands/console.rb50
-rw-r--r--railties/lib/rails/commands/dbconsole.rb134
-rw-r--r--railties/lib/rails/commands/runner.rb3
-rw-r--r--railties/lib/rails/commands/server.rb7
-rw-r--r--railties/lib/rails/configuration.rb10
-rw-r--r--railties/lib/rails/engine.rb28
-rw-r--r--railties/lib/rails/generators/actions.rb5
-rw-r--r--railties/lib/rails/generators/app_base.rb2
-rw-r--r--railties/lib/rails/generators/base.rb1
-rw-r--r--railties/lib/rails/generators/named_base.rb11
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt8
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/rails/controller/templates/controller.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb26
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile15
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Rakefile2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb4
-rw-r--r--railties/lib/rails/generators/test_case.rb12
-rw-r--r--railties/lib/rails/info_controller.rb32
-rw-r--r--railties/lib/rails/paths.rb4
-rw-r--r--railties/lib/rails/railtie.rb13
-rw-r--r--railties/lib/rails/tasks/documentation.rake2
-rw-r--r--railties/lib/rails/tasks/engine.rake2
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/tasks/routes.rake2
-rw-r--r--railties/lib/rails/tasks/statistics.rake1
-rw-r--r--railties/lib/rails/templates/layouts/application.html.erb32
-rw-r--r--railties/lib/rails/templates/rails/info/properties.html.erb1
-rw-r--r--railties/lib/rails/templates/rails/info/routes.html.erb9
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/railties.gemspec7
-rw-r--r--railties/test/application/assets_test.rb41
-rw-r--r--railties/test/application/configuration_test.rb60
-rw-r--r--railties/test/application/rack/logger_test.rb2
-rw-r--r--railties/test/application/rake/notes_test.rb44
-rw-r--r--railties/test/application/rake_test.rb46
-rw-r--r--railties/test/application/routing_test.rb12
-rw-r--r--railties/test/application/runner_test.rb10
-rw-r--r--railties/test/commands/console_test.rb93
-rw-r--r--railties/test/commands/dbconsole_test.rb61
-rw-r--r--railties/test/commands/server_test.rb4
-rw-r--r--railties/test/configuration/middleware_stack_proxy_test.rb59
-rw-r--r--railties/test/generators/actions_test.rb8
-rw-r--r--railties/test/generators/app_generator_test.rb2
-rw-r--r--railties/test/generators/assets_generator_test.rb1
-rw-r--r--railties/test/generators/namespaced_generators_test.rb8
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb48
-rw-r--r--railties/test/generators_test.rb4
-rw-r--r--railties/test/isolation/abstract_unit.rb22
-rw-r--r--railties/test/paths_test.rb4
-rw-r--r--railties/test/rails_info_controller_test.rb17
-rw-r--r--railties/test/railties/engine_test.rb8
-rw-r--r--railties/test/railties/generators_test.rb8
-rw-r--r--railties/test/railties/railtie_test.rb16
481 files changed, 10209 insertions, 4256 deletions
diff --git a/Gemfile b/Gemfile
index ac835936ca..4e34d42fb6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,10 +8,11 @@ else
gem 'arel'
end
+gem 'minitest', '~> 3.1.0'
+gem 'mocha', '>= 0.11.2'
gem 'rack-test', github: "brynary/rack-test"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
-gem 'minitest', '~> 3.0.0'
if ENV['JOURNEY']
gem 'journey', path: ENV['JOURNEY']
@@ -29,9 +30,6 @@ end
# it being automatically loaded by sprockets
gem 'uglifier', '>= 1.0.3', require: false
-gem 'rake', '>= 0.8.7'
-gem 'mocha', '>= 0.11.2'
-
group :doc do
# The current sdoc cannot generate GitHub links due
# to a bug, but the PR that fixes it has been there
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index 7a77f9bba2..bdf0ffc2d0 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -176,7 +176,7 @@ You can do this, or ask the security team to do it.
Email the security reports to:
* rubyonrails-security@googlegroups.com
-* linux-distros@vs.openwall.org
+* oss-security@lists.openwall.com
Be sure to note the security fixes in your announcement along with CVE numbers
and links to each patch. Some people may not be able to upgrade right away,
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index a33d6e072b..a822412090 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,17 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu*
+
+## Rails 3.2.5 (Jun 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.4 (May 31, 2012) ##
+
+* No changes.
+
+
## Rails 3.2.3 (March 30, 2012) ##
* Upgrade mail version to 2.4.3 *ML*
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 90503edc20..0c669e2e91 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -7,6 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
s.required_ruby_version = '>= 1.9.3'
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 4f0cff0612..1899b13e71 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -184,6 +184,16 @@ module ActionMailer #:nodoc:
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
# with the filename +free_book.pdf+.
#
+ # If you need to send attachments with no content, you need to create an empty view for it,
+ # or add an empty body parameter like this:
+ #
+ # class ApplicationMailer < ActionMailer::Base
+ # def welcome(recipient)
+ # attachments['free_book.pdf'] = File.read('path/to/file.pdf')
+ # mail(:to => recipient, :subject => "New account information", :body => "")
+ # end
+ # end
+ #
# = Inline Attachments
#
# You can also specify that a file should be displayed inline with other HTML. This is useful
@@ -598,8 +608,10 @@ module ActionMailer #:nodoc:
# end
# end
#
- # Will look for all templates at "app/views/notifier" with name "welcome". However, those
- # can be customized:
+ # Will look for all templates at "app/views/notifier" with name "welcome".
+ # If no welcome template exists, it will raise an ActionView::MissingTemplate error.
+ #
+ # However, those can be customized:
#
# mail(:template_path => 'notifications', :template_name => 'another')
#
@@ -733,7 +745,11 @@ module ActionMailer #:nodoc:
def each_template(paths, name, &block) #:nodoc:
templates = lookup_context.find_all(name, Array(paths))
- templates.uniq { |t| t.formats }.each(&block)
+ if templates.empty?
+ raise ActionView::MissingTemplate.new([paths], name, [paths], false, 'mailer')
+ else
+ templates.uniq { |t| t.formats }.each(&block)
+ end
end
def create_parts_from_responses(m, responses) #:nodoc:
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 1d747ed18a..1b2e39b3f7 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -433,6 +433,13 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
end
+ test "should raise if missing template in implicit render" do
+ assert_raises ActionView::MissingTemplate do
+ BaseMailer.implicit_different_template('missing_template').deliver
+ end
+ assert_equal(0, BaseMailer.deliveries.length)
+ end
+
test "you can specify a different template for explicit render" do
mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
diff --git a/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb b/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/attachment_with_hash.html.erb
diff --git a/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb b/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/attachment_with_hash_default_encoding.html.erb
diff --git a/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb b/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_mailer/welcome_with_headers.html.erb
diff --git a/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/base_test/default_inline_attachment_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb b/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/mail_delivery_test/delivery_mailer/welcome.html.erb
diff --git a/actionmailer/test/fixtures/proc_mailer/welcome.html.erb b/actionmailer/test/fixtures/proc_mailer/welcome.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionmailer/test/fixtures/proc_mailer/welcome.html.erb
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 1e6e84ea4a..345b5aa330 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,64 @@
## Rails 4.0.0 (unreleased) ##
+* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping:
+
+ get Rack::Utils.escape('こんにちは') => 'home#index'
+
+ You just have to write the unicode route:
+
+ get 'こんにちは' => 'home#index'
+
+ *kennyj*
+
+* Return proper format on exceptions. *Santiago Pastorino*
+
+* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki*
+
+* Include mounted_helpers (helpers for accessing mounted engines) in ActionDispatch::IntegrationTest by default. *Piotr Sarnacki*
+
+* Extracted redirect logic from `ActionController::ForceSSL::ClassMethods.force_ssl` into `ActionController::ForceSSL#force_ssl_redirect`
+
+ *Jeremy Friesen*
+
+* Make possible to use a block in button_to helper if button text is hard
+ to fit into the name parameter, e.g.:
+
+ <%= button_to [:make_happy, @user] do %>
+ Make happy <strong><%= @user.name %></strong>
+ <% end %>
+ # => "<form method="post" action="/users/1/make_happy" class="button_to">
+ # <div>
+ # <button type="submit">
+ # Make happy <strong>Name</strong>
+ # </button>
+ # </div>
+ # </form>"
+
+ *Sergey Nartimov*
+
+* change a way of ordering helpers from several directories. Previously,
+ when loading helpers from multiple paths, all of the helpers files were
+ gathered into one array an then they were sorted. Helpers from different
+ directories should not be mixed before loading them to make loading more
+ predictable. The most common use case for such behavior is loading helpers
+ from engines. When you load helpers from application and engine Foo, in
+ that order, first rails will load all of the helpers from application,
+ sorted alphabetically and then it will do the same for Foo engine.
+
+ *Piotr Sarnacki*
+
+* `truncate` now always returns an escaped HTMl-safe string. The option `:escape` can be used as
+ false to not escape the result.
+
+ *Li Ellis Gallardo + Rafael Mendonça França*
+
+* `truncate` now accepts a block to show extra content when the text is truncated. *Li Ellis Gallardo*
+
+* Add `week_field`, `week_field_tag`, `month_field`, `month_field_tag`, `datetime_local_field`,
+ `datetime_local_field_tag`, `datetime_field` and `datetime_field_tag` helpers. *Carlos Galdino*
+
+* Add `color_field` and `color_field_tag` helpers. *Carlos Galdino*
+
* `assert_generates`, `assert_recognizes`, and `assert_routing` all raise
`Assertion` instead of `RoutingError` *David Chelimsky*
@@ -194,6 +253,38 @@
HTML5 `mark` element. *Brian Cardarella*
+## Rails 3.2.5 (Jun 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.4 (May 31, 2012) ##
+
+* Deprecate old APIs for highlight, excerpt and word_wrap *Jeremy Walker*
+
+* Deprecate `:disable_with` in favor of `'data-disable-with'` option for `button_to`, `button_tag` and `submit_tag` helpers.
+
+ *Carlos Galdino + Rafael Mendonça França*
+
+* Deprecate `:mouseover` option for `image_tag` helper. *Rafael Mendonça França*
+
+* Deprecate `button_to_function` and `link_to_function` helpers. *Rafael Mendonça França*
+
+* Don't break Haml with textarea newline fix. GH #393, #4000, #5190, #5191
+
+* Fix options handling on labels. GH #2492, #5614
+
+* Added config.action_view.embed_authenticity_token_in_remote_forms to deal
+ with regression from 16ee611fa
+
+* Set rendered_format when doing render :inline. GH #5632
+
+* Fix the redirect when it receive blocks with arity of 1. Closes #5677
+
+* Strip [nil] from parameters hash. Thanks to Ben Murphy for
+ reporting this! CVE-2012-2660
+
+
## Rails 3.2.3 (March 30, 2012) ##
* Add `config.action_view.embed_authenticity_token_in_remote_forms` (defaults to true) which allows to set if authenticity token will be included by default in remote forms. If you change it to false, you can still force authenticity token by passing `:authenticity_token => true` in form options *Piotr Sarnacki*
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 589a67dc02..938f3d6dc2 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -7,6 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).'
s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.'
s.required_ruby_version = '>= 1.9.3'
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
@@ -25,5 +26,5 @@ Gem::Specification.new do |s|
s.add_dependency('journey', '~> 1.0.1')
s.add_dependency('erubis', '~> 2.7.0')
- s.add_development_dependency('tzinfo', '~> 0.3.29')
+ s.add_development_dependency('tzinfo', '~> 0.3.33')
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 97a9eec144..9c3960961b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -51,7 +51,7 @@ module AbstractController
# to specify particular actions as hidden.
#
# ==== Returns
- # * <tt>array</tt> - An array of method names that should not be considered actions.
+ # * <tt>Array</tt> - An array of method names that should not be considered actions.
def hidden_actions
[]
end
@@ -63,7 +63,7 @@ module AbstractController
# itself. Finally, #hidden_actions are removed.
#
# ==== Returns
- # * <tt>set</tt> - A set of all methods that should be considered actions.
+ # * <tt>Set</tt> - A set of all methods that should be considered actions.
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
@@ -92,11 +92,12 @@ module AbstractController
# controller_path.
#
# ==== Returns
- # * <tt>string</tt>
+ # * <tt>String</tt>
def controller_path
@controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
end
+ # Refresh the cached action_methods when a new action_method is added.
def method_added(name)
super
clear_action_methods!
@@ -130,6 +131,7 @@ module AbstractController
self.class.controller_path
end
+ # Delegates to the class' #action_methods
def action_methods
self.class.action_methods
end
@@ -139,8 +141,14 @@ module AbstractController
#
# Notice that <tt>action_methods.include?("foo")</tt> may return
# false and <tt>available_action?("foo")</tt> returns true because
- # available action consider actions that are also available
+ # this method considers actions that are also available
# through other means, for example, implicit render ones.
+ #
+ # ==== Parameters
+ # * <tt>action_name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
def available_action?(action_name)
method_for_action(action_name).present?
end
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index 81fb514770..492329c401 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -4,7 +4,7 @@ module AbstractController
module Collector
def self.generate_method_for_mime(mime)
sym = mime.is_a?(Symbol) ? mime : mime.to_sym
- const = sym.to_s.upcase
+ const = sym.upcase
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{sym}(*args, &block) # def html(*args, &block)
custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block)
@@ -19,7 +19,7 @@ module AbstractController
protected
def method_missing(symbol, &block)
- mime_constant = Mime.const_get(symbol.to_s.upcase)
+ mime_constant = Mime.const_get(symbol.upcase)
if Mime::SET.include?(mime_constant)
AbstractController::Collector.generate_method_for_mime(mime_constant)
@@ -29,4 +29,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 4e0672d590..d63d17f4c5 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -132,7 +132,11 @@ module AbstractController
case arg
when String, Symbol
file_name = "#{arg.to_s.underscore}_helper"
- require_dependency(file_name, "Missing helper file helpers/%s.rb")
+ begin
+ require_dependency(file_name)
+ rescue LoadError => e
+ raise MissingHelperError.new(e, file_name)
+ end
file_name.camelize.constantize
when Module
arg
@@ -142,6 +146,15 @@ module AbstractController
end
end
+ class MissingHelperError < LoadError
+ def initialize(error, path)
+ @error = error
+ @path = "helpers/#{path}.rb"
+ set_backtrace error.backtrace
+ super("Missing helper file helpers/%s.rb" % path)
+ end
+ end
+
private
# Makes all the (instance) methods in the helper module available to templates
# rendered through this controller.
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index bc9f6fc3e8..c1b3994035 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -203,8 +203,7 @@ module AbstractController
include Rendering
included do
- class_attribute :_layout, :_layout_conditions,
- :instance_reader => false, :instance_writer => false
+ class_attribute :_layout, :_layout_conditions, :instance_accessor => false
self._layout = nil
self._layout_conditions = {}
_write_layout_method
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 112573a38d..9118806059 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -55,6 +55,9 @@ module ActionController #:nodoc:
end
end
+ include RackDelegation
+ include AbstractController::Callbacks
+
include ConfigMethods
include Pages, Actions, Fragments
include Sweeping if defined?(ActiveRecord)
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index dd4eddbe9a..73b8cd383c 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -110,7 +110,7 @@ module ActionController #:nodoc:
gzip_level = options.fetch(:gzip, page_cache_compression)
gzip_level = case gzip_level
when Symbol
- Zlib.const_get(gzip_level.to_s.upcase)
+ Zlib.const_get(gzip_level.upcase)
when Fixnum
gzip_level
when false
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index ac12cbb625..77d799a38a 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -40,15 +40,23 @@ module ActionController
def force_ssl(options = {})
host = options.delete(:host)
before_filter(options) do
- unless request.ssl?
- redirect_options = {:protocol => 'https://', :status => :moved_permanently}
- redirect_options.merge!(:host => host) if host
- redirect_options.merge!(:params => request.query_parameters)
- flash.keep if respond_to?(:flash)
- redirect_to redirect_options
- end
+ force_ssl_redirect(host)
end
end
end
+
+ # Redirect the existing request to use the HTTPS protocol.
+ #
+ # ==== Parameters
+ # * <tt>host</tt> - Redirect to a different host name
+ def force_ssl_redirect(host = nil)
+ unless request.ssl?
+ redirect_options = {:protocol => 'https://', :status => :moved_permanently}
+ redirect_options.merge!(:host => host) if host
+ redirect_options.merge!(:params => request.query_parameters)
+ flash.keep if respond_to?(:flash)
+ redirect_to redirect_options
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 86d061e3b7..66cdfd40ff 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -95,9 +95,9 @@ module ActionController
helpers = []
Array(path).each do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ helpers += names.sort
end
- helpers.sort!
helpers.uniq!
helpers
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 57bb0e2a32..a0d1064094 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -401,16 +401,20 @@ module ActionController
end
end
- # If token Authorization header is present, call the login procedure with
- # the present token and options.
+ # If token Authorization header is present, call the login
+ # procedure with the present token and options.
#
- # controller - ActionController::Base instance for the current request.
- # login_procedure - Proc to call if a token is present. The Proc should
- # take 2 arguments:
- # authenticate(controller) { |token, options| ... }
+ # [controller]
+ # ActionController::Base instance for the current request.
#
- # Returns the return value of `&login_procedure` if a token is found.
- # Returns nil if no token is found.
+ # [login_procedure]
+ # Proc to call if a token is present. The Proc should take two arguments:
+ #
+ # authenticate(controller) { |token, options| ... }
+ #
+ # Returns the return value of <tt>login_procedure</tt> if a
+ # token is found. Returns <tt>nil</tt> if no token is found.
+
def authenticate(controller, &login_procedure)
token, options = token_and_options(controller.request)
unless token.blank?
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 95b0e99ed5..53534c0307 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -14,6 +14,20 @@ module ActionController #:nodoc:
# authentication scheme there anyway). Also, GET requests are not protected as these
# should be idempotent.
#
+ # It's important to remember that XML or JSON requests are also affected and if
+ # you're building an API you'll need something like:
+ #
+ # class ApplicationController < ActionController::Base
+ # protect_from_forgery
+ # skip_before_filter :verify_authenticity_token, :if => :json_request?
+ #
+ # protected
+ #
+ # def json_request?
+ # request.format.json?
+ # end
+ # end
+ #
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
# which checks the token and resets the session if it doesn't match what was expected.
# A call to this method is generated for new \Rails applications by default.
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 5ee4c044ea..0b895e7860 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -84,17 +84,29 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
+ SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
+
+ def cache_control_headers
+ cache_control = {}
+ if cc = self[CACHE_CONTROL]
+ cc.delete(' ').split(',').each do |segment|
+ directive, argument = segment.split('=', 2)
+ case directive
+ when *SPESHUL_KEYS
+ key = directive.tr('-', '_')
+ cache_control[key.to_sym] = argument || true
+ else
+ cache_control[:extras] ||= []
+ cache_control[:extras] << segment
+ end
+ end
+ end
+ cache_control
+ end
def prepare_cache_control!
- @cache_control = {}
+ @cache_control = cache_control_headers
@etag = self[ETAG]
-
- if cache_control = self[CACHE_CONTROL]
- cache_control.split(/,\s*/).each do |segment|
- first, last = segment.split("=")
- @cache_control[first.to_sym] = last || true
- end
- end
end
def handle_conditional_get!
@@ -110,14 +122,24 @@ module ActionDispatch
MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
- return if self[CACHE_CONTROL].present?
+ control = {}
+ cc_headers = cache_control_headers
+ if extras = cc_headers.delete(:extras)
+ @cache_control[:extras] ||= []
+ @cache_control[:extras] += extras
+ @cache_control[:extras].uniq!
+ end
- control = @cache_control
+ control.merge! cc_headers
+ control.merge! @cache_control
if control.empty?
headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
headers[CACHE_CONTROL] = NO_CACHE
+ if control[:extras]
+ headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
+ end
else
extras = control[:extras]
max_age = control[:max_age]
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 0eaae80461..ee1913dbf9 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -99,9 +99,9 @@ module Mime
end
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.const_set(symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms))
+ Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
- SET << Mime.const_get(symbol.to_s.upcase)
+ SET << Mime.const_get(symbol.upcase)
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
@@ -194,7 +194,7 @@ module Mime
#
# Mime::Type.unregister(:mobile)
def unregister(symbol)
- symbol = symbol.to_s.upcase
+ symbol = symbol.upcase
mime = Mime.const_get(symbol)
Mime.instance_eval { remove_const(symbol) }
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index aa5ba3e8a5..8cea17c7a6 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -263,6 +263,27 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
+ protected
+
+ # Remove nils from the params hash
+ def deep_munge(hash)
+ hash.each_value do |v|
+ case v
+ when Array
+ v.grep(Hash) { |x| deep_munge(x) }
+ v.compact!
+ when Hash
+ deep_munge(v)
+ end
+ end
+
+ hash
+ end
+
+ def parse_query(qs)
+ deep_munge(super)
+ end
+
private
def check_method(name)
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 85b8d178bf..53bedaa40a 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -1,5 +1,4 @@
module ActionDispatch
- # A simple Rack application that renders exceptions in the given public path.
class PublicExceptions
attr_accessor :public_path
@@ -8,23 +7,41 @@ module ActionDispatch
end
def call(env)
- status = env["PATH_INFO"][1..-1]
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html"
-
- if locale_path && File.exist?(locale_path)
- render(status, File.read(locale_path))
- elsif File.exist?(path)
- render(status, File.read(path))
+ exception = env["action_dispatch.exception"]
+ status = env["PATH_INFO"][1..-1]
+ request = ActionDispatch::Request.new(env)
+ content_type = request.formats.first
+ body = { :status => status, :error => exception.message }
+
+ render(status, content_type, body)
+ end
+
+ private
+
+ def render(status, content_type, body)
+ format = content_type && "to_#{content_type.to_sym}"
+ if format && body.respond_to?(format)
+ render_format(status, content_type, body.public_send(format))
else
- [404, { "X-Cascade" => "pass" }, []]
+ render_html(status)
end
end
- private
+ def render_format(status, content_type, body)
+ [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
+ 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ def render_html(status)
+ found = false
+ path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
+ path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ if found || File.exist?(path)
+ render_format(status, 'text/html', File.read(path))
+ else
+ [404, { "X-Cascade" => "pass" }, []]
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 4ad7071820..d8bcc28613 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -87,6 +87,14 @@ module ActionDispatch
alias :key? :has_key?
alias :include? :has_key?
+ def keys
+ @delegate.keys
+ end
+
+ def values
+ @delegate.values
+ end
+
def []=(key, value)
load_for_write!
@delegate[key.to_s] = value
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 38a0270151..29090882a5 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,3 +1,4 @@
+# encoding: UTF-8
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
@@ -218,6 +219,12 @@ module ActionDispatch
#
# match "/stories" => redirect("/posts")
#
+ # == Unicode character routes
+ #
+ # You can specify unicode character routes in your router:
+ #
+ # match "こんにちは" => "welcome#index"
+ #
# == Routing to Rack Applications
#
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 67a208263b..94242ad962 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1318,7 +1318,7 @@ module ActionDispatch
def draw(name)
path = @draw_paths.find do |_path|
- _path.join("#{name}.rb").file?
+ File.exists? "#{_path}/#{name}.rb"
end
unless path
@@ -1327,9 +1327,9 @@ module ActionDispatch
msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
raise ArgumentError, msg
end
-
- route_path = path.join("#{name}.rb")
- instance_eval(route_path.read, route_path.to_s)
+
+ route_path = "#{path}/#{name}.rb"
+ instance_eval(File.read(route_path), route_path.to_s)
end
# match 'path' => 'controller#action'
@@ -1387,7 +1387,7 @@ module ActionDispatch
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.new(@set, @scope, path, options)
+ mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 8fde667108..86ce7a83b9 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -95,7 +95,7 @@ module ActionDispatch
end
record = extract_record(record_or_hash_or_array)
- record = record.to_model if record.respond_to?(:to_model)
+ record = convert_to_model(record)
args = Array === record_or_hash_or_array ?
record_or_hash_or_array.dup :
@@ -122,6 +122,8 @@ module ActionDispatch
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
+ args.collect! { |a| convert_to_model(a) }
+
(proxy || self).send(named_route, *args)
end
@@ -152,6 +154,10 @@ module ActionDispatch
options[:action] ? "#{options[:action]}_" : ''
end
+ def convert_to_model(object)
+ object.respond_to?(:to_model) ? object.to_model : object
+ end
+
def routing_type(options)
options[:routing_type] || :url
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 7872f4007e..64b1d58ae9 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -252,15 +252,11 @@ module ActionDispatch
self.draw_paths = []
self.request_class = request_class
- @valid_conditions = {}
-
+ @valid_conditions = { :controller => true, :action => true }
request_class.public_instance_methods.each { |m|
- @valid_conditions[m.to_sym] = true
+ @valid_conditions[m] = true
}
- @valid_conditions[:controller] = true
- @valid_conditions[:action] = true
-
- self.valid_conditions.delete(:id)
+ @valid_conditions.delete(:id)
@append = []
@prepend = []
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 08fd28d72d..50ca28395b 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -17,8 +17,8 @@ module ActionDispatch
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
- # automatically be upcased, with the prefix 'HTTP_' added if needed.
+ # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # merged into the Rack env hash.
#
# This method returns an Response object, which one can use to
# inspect the details of the response. Furthermore, if this method was
@@ -73,8 +73,7 @@ module ActionDispatch
#
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
- # string; the headers are a hash. Keys are automatically upcased and
- # prefixed with 'HTTP_' if not already.
+ # string; the headers are a hash.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -194,8 +193,11 @@ module ActionDispatch
# If the app is a Rails app, make url_helpers available on the session
# This makes app.url_for and app.foo_path available in the console
- if app.respond_to?(:routes) && app.routes.respond_to?(:url_helpers)
- singleton_class.class_eval { include app.routes.url_helpers }
+ if app.respond_to?(:routes)
+ singleton_class.class_eval do
+ include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
+ include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
+ end
end
reset!
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index d04be2099c..a86b510719 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -11,7 +11,7 @@ module ActionDispatch
end
def initialize(env = {})
- env = Rails.application.env_config.merge(env) if defined?(Rails.application)
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
super(DEFAULT_ENV.merge(env))
self.host = 'test.host'
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 3823f87027..03dfa110e3 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -36,7 +36,7 @@ module ActionView
autoload :LookupContext
autoload :PathSet
autoload :Template
- autoload :TestCase
+
autoload_under "renderer" do
autoload :Renderer
@@ -73,6 +73,8 @@ module ActionView
end
end
+ autoload :TestCase
+
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
end
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 4ce41d51f1..81880d17ea 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -35,7 +35,13 @@ module ActionView
# Return the filesystem path for the source
def compute_source_path(source, dir, ext)
source = rewrite_extension(source, dir, ext) if ext
- File.join(config.assets_dir, dir, source)
+
+ sources = []
+ sources << config.assets_dir
+ sources << dir unless source[0] == ?/
+ sources << source
+
+ File.join(sources)
end
def is_uri?(path)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
index 05d5f1870a..1a54ca2399 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -7,7 +7,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class AssetIncludeTag
+ class AssetIncludeTag #:nodoc:
include TagHelper
attr_reader :config, :asset_paths
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 4292d29f60..e0dbfe62c6 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -6,7 +6,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class JavascriptIncludeTag < AssetIncludeTag
+ class JavascriptIncludeTag < AssetIncludeTag #:nodoc:
def asset_name
'javascript'
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index 57b0627225..91318b2812 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -6,7 +6,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class StylesheetIncludeTag < AssetIncludeTag
+ class StylesheetIncludeTag < AssetIncludeTag #:nodoc:
def asset_name
'stylesheet'
end
@@ -53,7 +53,7 @@ module ActionView
# If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
- #
+ #
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb
index 1a583e62ae..74ef25f7c1 100644
--- a/actionpack/lib/action_view/helpers/controller_helper.rb
+++ b/actionpack/lib/action_view/helpers/controller_helper.rb
@@ -10,14 +10,16 @@ module ActionView
delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
:flash, :action_name, :controller_name, :controller_path, :to => :controller
- delegate :logger, :to => :controller, :allow_nil => true
-
def assign_controller(controller)
if @_controller = controller
@_request = controller.request if controller.respond_to?(:request)
@_config = controller.config.inheritable_copy if controller.respond_to?(:config)
end
end
+
+ def logger
+ controller.logger if controller.respond_to?(:logger)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6510610034..7dd35f7357 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -342,7 +342,7 @@ module ActionView
# Example:
#
# <%= form_for(@post) do |f| %>
- # <% f.fields_for(:comments, :include_id => false) do |cf| %>
+ # <%= f.fields_for(:comments, :include_id => false) do |cf| %>
# ...
# <% end %>
# <% end %>
@@ -939,6 +939,15 @@ module ActionView
Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end
+ # Returns a text_field of type "color".
+ #
+ # color_field("car", "color")
+ # # => <input id="car_color" name="car[color]" type="color" value="#000000" />
+ #
+ def color_field(object_name, method, options = {})
+ Tags::ColorField.new(object_name, method, self, options).render
+ end
+
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
# some browsers.
@@ -1007,6 +1016,74 @@ module ActionView
Tags::TimeField.new(object_name, method, self, options).render
end
+ # Returns a text_field of type "datetime".
+ #
+ # datetime_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 1, 12)
+ # datetime_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
+ #
+ def datetime_field(object_name, method, options = {})
+ Tags::DatetimeField.new(object_name, method, self, options).render
+ end
+
+ # Returns a text_field of type "datetime-local".
+ #
+ # datetime_local_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 1, 12)
+ # datetime_local_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
+ #
+ def datetime_local_field(object_name, method, options = {})
+ Tags::DatetimeLocalField.new(object_name, method, self, options).render
+ end
+
+ # Returns a text_field of type "month".
+ #
+ # month_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="month" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-%m"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 1, 27)
+ # month_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
+ #
+ def month_field(object_name, method, options = {})
+ Tags::MonthField.new(object_name, method, self, options).render
+ end
+
+ # Returns a text_field of type "week".
+ #
+ # week_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="week" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-W%W"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 5, 12)
+ # week_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
+ #
+ def week_field(object_name, method, options = {})
+ Tags::WeekField.new(object_name, method, self, options).render
+ end
+
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
@@ -1234,10 +1311,21 @@ module ActionView
# post:
# create: "Add %{model}"
#
- def button(value=nil, options={})
+ # ==== Examples
+ # button("Create a post")
+ # # => <button name='button' type='submit'>Create post</button>
+ #
+ # button do
+ # content_tag(:strong, 'Ask me!')
+ # end
+ # # => <button name='button' type='submit'>
+ # # <strong>Ask me!</strong>
+ # # </button>
+ #
+ def button(value = nil, options = {}, &block)
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
- @template.button_tag(value, options)
+ @template.button_tag(value, options, &block)
end
def emitted_hidden_id?
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index e65b4e3e95..1a0019a48c 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -524,6 +524,14 @@ module ActionView
output.safe_concat("</fieldset>")
end
+ # Creates a text field of type "color".
+ #
+ # ==== Options
+ # * Accepts the same options as text_field_tag.
+ def color_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
+ end
+
# Creates a text field of type "search".
#
# ==== Options
@@ -560,6 +568,50 @@ module ActionView
text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
end
+ # Creates a text field of type "datetime".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def datetime_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "datetime"))
+ end
+
+ # Creates a text field of type "datetime-local".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def datetime_local_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local"))
+ end
+
+ # Creates a text field of type "month".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def month_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "month"))
+ end
+
+ # Creates a text field of type "week".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def week_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "week"))
+ end
+
# Creates a text field of type "url".
#
# ==== Options
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index dfc26acfad..8f97d1f014 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,8 +1,8 @@
# encoding: utf-8
-require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/number_helper'
module ActionView
# = Action View Number Helpers
@@ -16,9 +16,6 @@ module ActionView
# unchanged if can't be converted into a valid number.
module NumberHelper
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
- :precision => 2, :significant => false, :strip_insignificant_zeros => false }
-
# Raised when argument +number+ param given to the helpers is invalid and
# the option :raise is set to +true+.
class InvalidNumberError < StandardError
@@ -63,25 +60,7 @@ module ActionView
options = options.symbolize_keys
parse_float(number, true) if options[:raise]
-
- number = number.to_s.strip
- area_code = options[:area_code]
- delimiter = options[:delimiter] || "-"
- extension = options[:extension]
- country_code = options[:country_code]
-
- if area_code
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
- else
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
- end
-
- str = ''
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
- str << number
- str << " x #{extension}" unless extension.blank?
- ERB::Util.html_escape(str)
+ ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
end
# Formats a +number+ into a currency string (e.g., $13.65). You
@@ -128,34 +107,9 @@ module ActionView
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- currency = translations_for('currency', options[:locale])
- currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
- defaults[:negative_format] = "-" + options[:format] if options[:format]
- options = defaults.merge!(options)
-
- unit = options.delete(:unit)
- format = options.delete(:format)
-
- if number.to_f < 0
- format = options.delete(:negative_format)
- number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
- end
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub('%n', value).gsub('%u', unit).html_safe
- rescue InvalidNumberError => e
- if options[:raise]
- raise
- else
- formatted_number = format.gsub('%n', e.number).gsub('%u', unit)
- e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
- end
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_currency(number, options) }
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -196,24 +150,9 @@ module ActionView
# number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- defaults = format_translations('percentage', options[:locale])
- options = defaults.merge!(options)
-
- format = options[:format] || "%n%"
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub(/%n/, value).html_safe
- rescue InvalidNumberError => e
- if options[:raise]
- raise
- else
- formatted_number = format.gsub(/%n/, e.number)
- e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
- end
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_percentage(number, options) }
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -246,15 +185,9 @@ module ActionView
#
# number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- parse_float(number, options[:raise]) or return number
-
- options = defaults_translations(options[:locale]).merge(options)
-
- parts = number.to_s.to_str.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- safe_join(parts, options[:separator])
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_delimited(number, options) }
end
# Formats a +number+ with the specified level of
@@ -299,41 +232,11 @@ module ActionView
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('precision', options[:locale])
- options = defaults.merge!(options)
-
- precision = options.delete :precision
- significant = options.delete :significant
- strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
- if significant and precision > 0
- if number == 0
- digits, rounded_number = 1, 0
- else
- digits = (Math.log10(number.abs) + 1).floor
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
- digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
- end
- precision -= digits
- precision = precision > 0 ? precision : 0 #don't let it be negative
- else
- rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
- end
- formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
- if strip_insignificant_zeros
- escaped_separator = Regexp.escape(options[:separator])
- formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe
- else
- formatted_number
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_rounded(number, options) }
end
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
# Formats the bytes in +number+ into a more understandable
# representation (e.g., giving it 1500 yields 1.5 KB). This
@@ -383,40 +286,11 @@ module ActionView
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = options.symbolize_keys
-
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
-
- #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)
-
- storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- base = options[:prefix] == :si ? 1000 : 1024
-
- if number.to_i < base
- unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
- else
- max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= base ** exponent
-
- unit_key = STORAGE_UNITS[exponent]
- unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
- formatted_number = number_with_precision(number, options)
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human_size(number, options) }
end
- 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}.freeze
-
# Pretty prints (formats and approximates) a number in a way it
# is more readable by humans (eg.: 1200000000 becomes "1.2
# Billion"). This is useful for numbers that can get very large
@@ -516,60 +390,34 @@ module ActionView
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human(number, options) }
+ end
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
+ private
- #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)
+ def escape_unsafe_delimiters_and_separators(options)
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
+ options
+ end
- inverted_du = DECIMAL_UNITS.invert
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
+ valid_float = valid_float?(number)
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
- units = options.delete :units
- unit_exponents = case units
- when Hash
- units
- when String, Symbol
- I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
- when nil
- I18n.translate(:"number.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}
+ formatted_number = yield
- number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
- number /= 10 ** display_exponent
-
- unit = case units
- when Hash
- units[DECIMAL_UNITS[display_exponent]]
- when String, Symbol
- I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ if valid_float || number.html_safe?
+ formatted_number.html_safe
else
- I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ formatted_number
end
-
- decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
- formatted_number = number_with_precision(number, options)
- decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
- end
-
- private
-
- def format_translations(namespace, locale)
- defaults_translations(locale).merge(translations_for(namespace, locale))
- end
-
- def defaults_translations(locale)
- I18n.translate(:'number.format', :locale => locale, :default => {})
end
- def translations_for(namespace, locale)
- I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ def valid_float?(number)
+ !parse_float(number, false).nil?
end
def parse_float(number, raise_error)
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
index 5cd77c8ec3..a05e16979a 100644
--- a/actionpack/lib/action_view/helpers/tags.rb
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -8,14 +8,18 @@ module ActionView
autoload :CollectionCheckBoxes
autoload :CollectionRadioButtons
autoload :CollectionSelect
+ autoload :ColorField
autoload :DateField
autoload :DateSelect
+ autoload :DatetimeField
+ autoload :DatetimeLocalField
autoload :DatetimeSelect
autoload :EmailField
autoload :FileField
autoload :GroupedCollectionSelect
autoload :HiddenField
autoload :Label
+ autoload :MonthField
autoload :NumberField
autoload :PasswordField
autoload :RadioButton
@@ -29,6 +33,7 @@ module ActionView
autoload :TimeSelect
autoload :TimeZoneSelect
autoload :UrlField
+ autoload :WeekField
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/color_field.rb b/actionpack/lib/action_view/helpers/tags/color_field.rb
new file mode 100644
index 0000000000..6f08f8483a
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/color_field.rb
@@ -0,0 +1,25 @@
+module ActionView
+ module Helpers
+ module Tags
+ class ColorField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { validate_color_string(value(object)) }
+ @options = options
+ super
+ end
+
+ private
+
+ def validate_color_string(string)
+ regex = /#[0-9a-fA-F]{6}/
+ if regex.match(string)
+ string.downcase
+ else
+ "#000000"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb
index 0e79609d52..64c29dea3d 100644
--- a/actionpack/lib/action_view/helpers/tags/date_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/date_field.rb
@@ -1,13 +1,12 @@
module ActionView
module Helpers
module Tags
- class DateField < TextField #:nodoc:
- def render
- options = @options.stringify_keys
- options["value"] = @options.fetch("value") { value(object).try(:to_date) }
- @options = options
- super
- end
+ class DateField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m-%d")
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_field.rb
new file mode 100644
index 0000000000..e407146e96
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_field.rb
@@ -0,0 +1,22 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { format_date(value(object)) }
+ options["min"] = format_date(options["min"])
+ options["max"] = format_date(options["max"])
+ @options = options
+ super
+ end
+
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m-%dT%T.%L%z")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
new file mode 100644
index 0000000000..6668d6d718
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
@@ -0,0 +1,19 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeLocalField < DatetimeField #:nodoc:
+ class << self
+ def field_type
+ @field_type ||= "datetime-local"
+ end
+ end
+
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m-%dT%T")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/month_field.rb b/actionpack/lib/action_view/helpers/tags/month_field.rb
new file mode 100644
index 0000000000..3d3c32d847
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/month_field.rb
@@ -0,0 +1,13 @@
+module ActionView
+ module Helpers
+ module Tags
+ class MonthField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb
index 271dc00c54..a3941860c9 100644
--- a/actionpack/lib/action_view/helpers/tags/time_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/time_field.rb
@@ -1,13 +1,12 @@
module ActionView
module Helpers
module Tags
- class TimeField < TextField #:nodoc:
- def render
- options = @options.stringify_keys
- options["value"] = @options.fetch("value") { value(object).try(:strftime, "%T.%L") }
- @options = options
- super
- end
+ class TimeField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%T.%L")
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionpack/lib/action_view/helpers/tags/week_field.rb
new file mode 100644
index 0000000000..1e13939a0a
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/week_field.rb
@@ -0,0 +1,13 @@
+module ActionView
+ module Helpers
+ module Tags
+ class WeekField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-W%W")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 8cd7cf0052..0cc0d069ea 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -62,9 +62,11 @@ module ActionView
#
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
#
- # The result is not marked as HTML-safe, so will be subject to the default escaping when
- # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
- # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
+ # Pass a block if you want to show extra content when the text is truncated.
+ #
+ # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
+ # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
+ # may produce invalid HTML (such as unbalanced or incomplete tags).
#
# truncate("Once upon a time in a world far far away")
# # => "Once upon a time in a world..."
@@ -80,8 +82,18 @@ module ActionView
#
# truncate("<p>Once upon a time in a world far far away</p>")
# # => "<p>Once upon a time in a wo..."
- def truncate(text, options = {})
- text.truncate(options.fetch(:length, 30), options) if text
+ #
+ # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
+ # # => "Once upon a time in a wo...<a href="#">Continue</a>"
+ def truncate(text, options = {}, &block)
+ if text
+ length = options.fetch(:length, 30)
+
+ content = text.truncate(length, options)
+ content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
+ content << capture(&block) if block_given? && text.length > length
+ content
+ end
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
@@ -102,7 +114,7 @@ module ActionView
# # => You searched for: <a href="search?q=rails">rails</a>
def highlight(text, phrases, options = {})
highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
-
+
text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
@@ -165,12 +177,12 @@ module ActionView
# pluralize(0, 'person')
# # => 0 people
def pluralize(count, singular, plural = nil)
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
- singular
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
+ singular
else
plural || singular.pluralize
end
-
+
"#{count || 0} #{word}"
end
@@ -215,7 +227,7 @@ module ActionView
#
# simple_format(my_text)
# # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
- #
+ #
# simple_format(my_text, {}, :wrapper_tag => "div")
# # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
#
@@ -231,7 +243,7 @@ module ActionView
# # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options = {}, options = {})
wrapper_tag = options.fetch(:wrapper_tag, :p)
-
+
text = sanitize(text) if options.fetch(:sanitize, true)
paragraphs = split_paragraphs(text)
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 8171bea8ed..552c9ba660 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -64,7 +64,7 @@ module ActionView
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
#
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
+ # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
# for more information.
def localize(*args)
I18n.localize(*args)
@@ -96,7 +96,7 @@ module ActionView
new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
break
else
- new_defautls << key
+ new_defaults << key
end
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 7e69547dab..7f5b3c8a0f 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -233,25 +233,15 @@ module ActionView
#
# link_to("Destroy", "http://www.example.com", :method => :delete, :confirm => "Are you sure?")
# # => <a href='http://www.example.com' rel="nofollow" data-method="delete" data-confirm="Are you sure?">Destroy</a>
- def link_to(*args, &block)
- if block_given?
- options = args.first || {}
- html_options = args.second
- link_to(capture(&block), options, html_options)
- else
- name = args[0]
- options = args[1] || {}
- html_options = args[2]
-
- html_options = convert_options_to_data_attributes(options, html_options)
- url = url_for(options)
+ def link_to(name = nil, options = nil, html_options = nil, &block)
+ html_options, options = options, name if block_given?
+ options ||= {}
+ url = url_for(options)
- href = html_options['href']
- tag_options = tag_options(html_options)
+ html_options = convert_options_to_data_attributes(options, html_options)
+ html_options['href'] ||= url
- href_attr = "href=\"#{ERB::Util.html_escape(url)}\"" unless href
- "<a #{href_attr}#{tag_options}>#{ERB::Util.html_escape(name || url)}</a>".html_safe
- end
+ content_tag(:a, name || url, html_options, &block)
end
# Generates a form containing a single button that submits to the URL created
@@ -294,6 +284,16 @@ module ActionView
# # <div><input value="New" type="submit" /></div>
# # </form>"
#
+ # <%= button_to [:make_happy, @user] do %>
+ # Make happy <strong><%= @user.name %></strong>
+ # <% end %>
+ # # => "<form method="post" action="/users/1/make_happy" class="button_to">
+ # # <div>
+ # # <button type="submit">
+ # # Make happy <strong><%= @user.name %></strong>
+ # # </button>
+ # # </div>
+ # # </form>"
#
# <%= button_to "New", :action => "new", :form_class => "new-thing" %>
# # => "<form method="post" action="/controller/new" class="new-thing">
@@ -331,7 +331,11 @@ module ActionView
# # </div>
# # </form>"
# #
- def button_to(name, options = {}, html_options = {})
+ def button_to(name = nil, options = nil, html_options = nil, &block)
+ html_options, options = options, name if block_given?
+ options ||= {}
+ html_options ||= {}
+
html_options = html_options.stringify_keys
convert_boolean_attributes!(html_options, %w(disabled))
@@ -350,9 +354,16 @@ module ActionView
request_token_tag = form_method == 'post' ? token_tag : ''
html_options = convert_options_to_data_attributes(options, html_options)
- html_options.merge!("type" => "submit", "value" => name || url)
+ html_options['type'] = 'submit'
+
+ button = if block_given?
+ content_tag('button', html_options, &block)
+ else
+ html_options['value'] = name || url
+ tag('input', html_options)
+ end
- inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag
+ inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
content_tag('form', content_tag('div', inner_tags), form_options)
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 8e9db634fb..8a56f147b8 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -1,102 +1,4 @@
"en":
- number:
- # Used in number_with_delimiter()
- # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
- format:
- # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
- separator: "."
- # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
- delimiter: ","
- # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
- precision: 3
- # If set to true, precision will mean the number of significant digits instead
- # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
- significant: false
- # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
- strip_insignificant_zeros: false
-
- # Used in number_to_currency()
- currency:
- format:
- # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
- format: "%u%n"
- unit: "$"
- # These five are to override number.format and are optional
- separator: "."
- delimiter: ","
- precision: 2
- significant: false
- strip_insignificant_zeros: false
-
- # Used in number_to_percentage()
- percentage:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- # precision:
- # significant: false
- # strip_insignificant_zeros: false
- format: "%n%"
-
- # Used in number_to_precision()
- precision:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- # precision:
- # significant: false
- # strip_insignificant_zeros: false
-
- # Used in number_to_human_size() and number_to_human()
- human:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- precision: 3
- significant: true
- strip_insignificant_zeros: true
- # Used in number_to_human_size()
- storage_units:
- # Storage units output formatting.
- # %u is the storage unit, %n is the number (default: 2 MB)
- format: "%n %u"
- units:
- byte:
- one: "Byte"
- other: "Bytes"
- kb: "KB"
- mb: "MB"
- gb: "GB"
- tb: "TB"
- # Used in number_to_human()
- decimal_units:
- format: "%n %u"
- # Decimal units output formatting
- # By default we will only quantify some of the exponents
- # but the commented ones might be defined or overridden
- # by the user.
- units:
- # femto: Quadrillionth
- # pico: Trillionth
- # nano: Billionth
- # micro: Millionth
- # mili: Thousandth
- # centi: Hundredth
- # deci: Tenth
- unit: ""
- # ten:
- # one: Ten
- # other: Tens
- # hundred: Hundred
- thousand: Thousand
- million: Million
- billion: Billion
- trillion: Trillion
- quadrillion: Quadrillion
-
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
distance_in_words:
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index ae923de24e..82892593f8 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -35,7 +35,7 @@ module ActionView
end
end
- # Renders the given template. An string representing the layout can be
+ # Renders the given template. A string representing the layout can be
# supplied as well.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
view, locals = @view, locals || {}
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index ab09dbece4..047e3fb1f9 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -8,7 +8,7 @@ module ActionView
# = Action View Resolver
class Resolver
# Keeps all information about view path and builds virtual path.
- class Path < String
+ class Path
attr_reader :name, :prefix, :partial, :virtual
alias_method :partial?, :partial
@@ -20,9 +20,16 @@ module ActionView
end
def initialize(name, prefix, partial, virtual)
- @name, @prefix, @partial = name, prefix, partial
- super(virtual)
+ @name = name
+ @prefix = prefix
+ @partial = partial
+ @virtual = virtual
end
+
+ def to_str
+ @virtual
+ end
+ alias :to_s :to_str
end
# Threadsafe template cache
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index fece499c94..53bde48e4d 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -229,7 +229,8 @@ module ActionView
def method_missing(selector, *args)
if @controller.respond_to?(:_routes) &&
- @controller._routes.named_routes.helpers.include?(selector)
+ ( @controller._routes.named_routes.helpers.include?(selector) ||
+ @controller._routes.mounted_helpers.method_defined?(selector) )
@controller.__send__(selector, *args)
else
super
diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb
index 9a7445de7b..e79008fa9d 100644
--- a/actionpack/test/abstract/helper_test.rb
+++ b/actionpack/test/abstract/helper_test.rb
@@ -69,7 +69,12 @@ module AbstractController
end
def test_declare_missing_helper
- assert_raise(MissingSourceFile) { AbstractHelpers.helper :missing }
+ begin
+ AbstractHelpers.helper :missing
+ flunk "should have raised an exception"
+ rescue LoadError => e
+ assert_equal "helpers/missing_helper.rb", e.path
+ end
end
def test_helpers_with_module_through_block
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index ba06bcae51..37deb9c98a 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -277,6 +277,7 @@ module ActionController
include ActionController::Testing
# This stub emulates the Railtie including the URL helpers from a Rails application
include SharedTestRoutes.url_helpers
+ include SharedTestRoutes.mounted_helpers
self.view_paths = FIXTURE_LOAD_PATH
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index 90e7f4ae59..afb714484b 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -25,6 +25,24 @@ class Series < ActiveRecord::Base
self.table_name = 'projects'
end
+class ModelDelegator < ActiveRecord::Base
+ self.table_name = 'projects'
+
+ def to_model
+ ModelDelegate.new
+ end
+end
+
+class ModelDelegate
+ def self.model_name
+ ActiveModel::Name.new(self)
+ end
+
+ def to_param
+ 'overridden'
+ end
+end
+
module Blog
class Post < ActiveRecord::Base
self.table_name = 'projects'
@@ -50,6 +68,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
@bid = Bid.new
@tax = Tax.new
@fax = Fax.new
+ @delegator = ModelDelegator.new
@series = Series.new
@blog_post = Blog::Post.new
@blog_blog = Blog::Blog.new
@@ -439,6 +458,13 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_routing_a_to_model_delegate
+ with_test_routes do
+ @delegator.save
+ assert_equal "http://example.com/model_delegates/overridden", polymorphic_url(@delegator)
+ end
+ end
+
def with_namespaced_routes(name)
with_routing do |set|
set.draw do
@@ -469,6 +495,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
resource :bid
end
resources :series
+ resources :model_delegates
end
self.class.send(:include, @routes.url_helpers)
@@ -516,5 +543,4 @@ class PolymorphicRoutesTest < ActionController::TestCase
yield
end
end
-
end
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 5b423c8151..6758668b7a 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -49,6 +49,15 @@ class ForceSSLFlash < ForceSSLController
end
end
+class RedirectToSSL < ForceSSLController
+ def banana
+ force_ssl_redirect || render(:text => 'monkey')
+ end
+ def cheeseburger
+ force_ssl_redirect('secure.cheeseburger.host') || render(:text => 'ihaz')
+ end
+end
+
class ForceSSLControllerLevelTest < ActionController::TestCase
tests ForceSSLControllerLevel
@@ -149,3 +158,25 @@ class ForceSSLFlashTest < ActionController::TestCase
assert_equal "hello", assigns["flashy"]
end
end
+
+class RedirectToSSLTest < ActionController::TestCase
+ tests RedirectToSSL
+ def test_banana_redirects_to_https_if_not_https
+ get :banana
+ assert_response 301
+ assert_equal "https://test.host/redirect_to_ssl/banana", redirect_to_url
+ end
+
+ def test_cheeseburgers_redirects_to_https_with_new_host_if_not_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://secure.cheeseburger.host/redirect_to_ssl/cheeseburger", redirect_to_url
+ end
+
+ def test_banana_does_not_redirect_if_already_https
+ request.env['HTTPS'] = 'on'
+ get :cheeseburger
+ assert_response 200
+ assert_equal 'ihaz', response.body
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 757661d8d0..248c81193e 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -46,12 +46,42 @@ end
class MeTooController < JustMeController
end
+class HelpersPathsController < ActionController::Base
+ paths = ["helpers2_pack", "helpers1_pack"].map do |path|
+ File.join(File.expand_path('../../fixtures', __FILE__), path)
+ end
+ $:.unshift(*paths)
+
+ self.helpers_path = paths
+ helper :all
+
+ def index
+ render :inline => "<%= conflicting_helper %>"
+ end
+end
+
module LocalAbcHelper
def a() end
def b() end
def c() end
end
+class HelperPathsTest < ActiveSupport::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_helpers_paths_priority
+ request = ActionController::TestRequest.new
+ responses = HelpersPathsController.action(:index).call(request.env)
+
+ # helpers1_pack was given as a second path, so pack1_helper should be
+ # included as the second one
+ assert_equal "pack1", responses.last.body
+ end
+end
+
class HelperTest < ActiveSupport::TestCase
class TestController < ActionController::Base
attr_accessor :delegate_attr
@@ -79,13 +109,13 @@ class HelperTest < ActiveSupport::TestCase
def test_helper_method
assert_nothing_raised { @controller_class.helper_method :delegate_method }
- assert master_helper_methods.include?('delegate_method')
+ assert master_helper_methods.include?(:delegate_method)
end
def test_helper_attr
assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
- assert master_helper_methods.include?('delegate_attr')
- assert master_helper_methods.include?('delegate_attr=')
+ assert master_helper_methods.include?(:delegate_attr)
+ assert master_helper_methods.include?(:delegate_attr=)
end
def call_controller(klass, action)
@@ -130,16 +160,16 @@ class HelperTest < ActiveSupport::TestCase
end
def test_all_helpers
- methods = AllHelpersController._helpers.instance_methods.map {|m| m.to_s}
+ methods = AllHelpersController._helpers.instance_methods
# abc_helper.rb
- assert methods.include?('bare_a')
+ assert methods.include?(:bare_a)
# fun/games_helper.rb
- assert methods.include?('stratego')
+ assert methods.include?(:stratego)
# fun/pdf_helper.rb
- assert methods.include?('foobar')
+ assert methods.include?(:foobar)
end
def test_all_helpers_with_alternate_helper_dir
@@ -150,35 +180,35 @@ class HelperTest < ActiveSupport::TestCase
@controller_class.helper :all
# helpers/abc_helper.rb should not be included
- assert !master_helper_methods.include?('bare_a')
+ assert !master_helper_methods.include?(:bare_a)
# alternate_helpers/foo_helper.rb
- assert master_helper_methods.include?('baz')
+ assert master_helper_methods.include?(:baz)
end
def test_helper_proxy
- methods = AllHelpersController.helpers.methods.map(&:to_s)
+ methods = AllHelpersController.helpers.methods
# Action View
- assert methods.include?('pluralize')
+ assert methods.include?(:pluralize)
# abc_helper.rb
- assert methods.include?('bare_a')
+ assert methods.include?(:bare_a)
# fun/games_helper.rb
- assert methods.include?('stratego')
+ assert methods.include?(:stratego)
# fun/pdf_helper.rb
- assert methods.include?('foobar')
+ assert methods.include?(:foobar)
end
private
def expected_helper_methods
- TestHelper.instance_methods.map {|m| m.to_s }
+ TestHelper.instance_methods
end
def master_helper_methods
- @controller_class._helpers.instance_methods.map {|m| m.to_s }
+ @controller_class._helpers.instance_methods
end
def missing_methods
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index fb41dcb33a..f18bf33969 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -538,11 +538,26 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
@routes ||= ActionDispatch::Routing::RouteSet.new
end
+ class MountedApp
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ routes.draw do
+ get 'baz', :to => 'application_integration_test/test#index', :as => :baz
+ end
+
+ def self.call(*)
+ end
+ end
+
routes.draw do
get '', :to => 'application_integration_test/test#index', :as => :empty_string
get 'foo', :to => 'application_integration_test/test#index', :as => :foo
get 'bar', :to => 'application_integration_test/test#index', :as => :bar
+
+ mount MountedApp => '/mounted', :as => "mounted"
end
def app
@@ -555,6 +570,10 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
assert_equal '/bar', bar_path
end
+ test "includes mounted helpers" do
+ assert_equal '/mounted/baz', mounted.baz_path
+ end
+
test "route helpers after controller access" do
get '/'
assert_equal '/', empty_string_path
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 3d58c02338..6bebe7e1ed 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -116,6 +116,12 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
+ def conditional_hello_with_cache_control_headers
+ response.headers['Cache-Control'] = 'no-transform'
+ expires_now
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_bangs
render :action => 'hello_world'
end
@@ -724,6 +730,14 @@ class TestController < ActionController::Base
end
end
+class MetalTestController < ActionController::Metal
+ include ActionController::Rendering
+
+ def accessing_logger_in_template
+ render :inline => "<%= logger.class %>"
+ end
+end
+
class RenderTest < ActionController::TestCase
tests TestController
@@ -1512,6 +1526,12 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+ def test_expires_now_with_cache_control_headers
+ get :conditional_hello_with_cache_control_headers
+ assert_match(/no-cache/, @response.headers["Cache-Control"])
+ assert_match(/no-transform/, @response.headers["Cache-Control"])
+ end
+
def test_date_header_when_expires_in
time = Time.mktime(2011,10,30)
Time.stubs(:now).returns(time)
@@ -1605,3 +1625,12 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_response :success
end
end
+
+class MetalRenderTest < ActionController::TestCase
+ tests MetalTestController
+
+ def test_access_to_logger_in_view
+ get :accessing_logger_in_template
+ assert_equal "NilClass", @response.body
+ end
+end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 066cd523be..0289f4070b 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -9,7 +9,7 @@ module RequestForgeryProtectionActions
end
def show_button
- render :inline => "<%= button_to('New', '/') {} %>"
+ render :inline => "<%= button_to('New', '/') %>"
end
def external_form
@@ -79,7 +79,7 @@ class FreeCookieController < RequestForgeryProtectionController
end
def show_button
- render :inline => "<%= button_to('New', '/') {} %>"
+ render :inline => "<%= button_to('New', '/') %>"
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 2f552c3a5a..6cc1370105 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -270,6 +270,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
end
end
+ def test_specific_controller_action_failure
+ @rs.draw do
+ mount lambda {} => "/foo"
+ end
+
+ assert_raises(ActionController::RoutingError) do
+ url_for(@rs, :controller => "omg", :action => "lol")
+ end
+ end
+
def test_default_setup
@rs.draw { get '/:controller(/:action(/:id))' }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
@@ -1750,6 +1760,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
get 'account(/:action)' => "account#subscription"
get 'pages/:page_id/:controller(/:action(/:id))'
get ':controller/ping', :action => 'ping'
+ get 'こんにちは/世界', :controller => 'news', :action => 'index'
match ':controller(/:action(/:id))(.:format)', :via => :all
root :to => "news#index"
}
@@ -1866,6 +1877,10 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params)
end
+ def test_unicode_path
+ assert_equal({:controller => 'news', :action => 'index'}, @routes.recognize_path(URI.parser.escape('こんにちは/世界'), :method => :get))
+ end
+
private
def sort_extras!(extras)
if extras.length == 2
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 13ab19ed8f..351b9c4cfa 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -68,4 +68,29 @@ module ShowExceptions
assert_match(/boom/, body)
end
end
+
+ class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
+ def test_render_json_exception
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'application/json'
+ assert_response :internal_server_error
+ assert_equal 'application/json', response.content_type.to_s
+ assert_equal({ :status => '500', :error => 'boom!' }.to_json, response.body)
+ end
+
+ def test_render_xml_exception
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
+ assert_response :internal_server_error
+ assert_equal 'application/xml', response.content_type.to_s
+ assert_equal({ :status => '500', :error => 'boom!' }.to_xml, response.body)
+ end
+
+ def test_render_fallback_exception
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
+ assert_response :internal_server_error
+ assert_equal 'text/html', response.content_type.to_s
+ end
+ end
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index e3f9faaa64..9d77c3acc5 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -148,11 +148,11 @@ class MimeTypeTest < ActiveSupport::TestCase
types = Mime::SET.symbols.uniq - [:all, :iphone]
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
+ types.delete_if { |type| !Mime.const_defined?(type.upcase) }
types.each do |type|
- mime = Mime.const_get(type.to_s.upcase)
+ mime = Mime.const_get(type.upcase)
assert mime.respond_to?("#{type}?"), "#{mime.inspect} does not respond to #{type}?"
assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?"
invalid_types = types - [type]
@@ -170,10 +170,10 @@ class MimeTypeTest < ActiveSupport::TestCase
all_types = Mime::SET.symbols
all_types.uniq!
# Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
- all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) }
+ all_types.delete_if { |type| !Mime.const_defined?(type.upcase) }
verified, unverified = all_types.partition { |type| Mime::Type.browser_generated_types.include? type }
- assert verified.each { |type| assert Mime.const_get(type.to_s.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" }
- assert unverified.each { |type| assert !Mime.const_get(type.to_s.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" }
+ assert verified.each { |type| assert Mime.const_get(type.upcase).verify_request?, "Verifiable Mime Type is not verified: #{type.inspect}" }
+ assert unverified.each { |type| assert !Mime.const_get(type.upcase).verify_request?, "Nonverifiable Mime Type is verified: #{type.inspect}" }
end
test "references gives preference to symbols before strings" do
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index c3f009ab15..3cb430d83d 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -81,7 +81,16 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
end
test "query string without equal" do
- assert_parses({ "action" => nil }, "action")
+ assert_parses({"action" => nil}, "action")
+ assert_parses({"action" => {"foo" => nil}}, "action[foo]")
+ assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]")
+ assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]")
+ assert_parses({"action" => {"foo" => []}}, "action[foo][]")
+ assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]")
+ end
+
+ def test_array_parses_without_nil
+ assert_parses({"action" => ['1']}, "action[]=1&action[]")
end
test "query string with empty key" do
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 4d24456ba6..80d5a13171 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -36,6 +36,22 @@ module ActionDispatch
assert_equal s, Session.find(env)
end
+ def test_keys
+ env = {}
+ s = Session.create(store, env, {})
+ s['rails'] = 'ftw'
+ s['adequate'] = 'awesome'
+ assert_equal %w[rails adequate], s.keys
+ end
+
+ def test_values
+ env = {}
+ s = Session.create(store, env, {})
+ s['rails'] = 'ftw'
+ s['adequate'] = 'awesome'
+ assert_equal %w[ftw awesome], s.values
+ end
+
private
def store
Class.new {
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 00d09282ca..f15dd7214b 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -2331,7 +2331,7 @@ class TestDrawExternalFile < ActionDispatch::IntegrationTest
end
end
- DRAW_PATH = Pathname.new(File.expand_path('../../fixtures/routes', __FILE__))
+ DRAW_PATH = File.expand_path('../../fixtures/routes', __FILE__)
DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw_paths << DRAW_PATH
@@ -2472,7 +2472,7 @@ end
class TestUnicodePaths < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
- get "/#{Rack::Utils.escape("ほげ")}" => lambda { |env|
+ get "/ほげ" => lambda { |env|
[200, { 'Content-Type' => 'text/plain' }, []]
}, :as => :unicode_path
end
@@ -2727,4 +2727,4 @@ class TestInvalidUrls < ActionDispatch::IntegrationTest
assert_response :bad_request
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
index 4ee1d61146..6047631ba3 100644
--- a/actionpack/test/dispatch/test_request_test.rb
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -55,6 +55,13 @@ class TestRequestTest < ActiveSupport::TestCase
assert_cookies({"user_name" => "david"}, req.cookie_jar)
end
+ test "does not complain when Rails.application is nil" do
+ Rails.stubs(:application).returns(nil)
+ req = ActionDispatch::TestRequest.new
+
+ assert_equal false, req.env.empty?
+ end
+
private
def assert_cookies(expected, cookie_jar)
assert_equal(expected, cookie_jar.instance_variable_get("@cookies"))
diff --git a/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
new file mode 100644
index 0000000000..9faa427736
--- /dev/null
+++ b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
@@ -0,0 +1,5 @@
+module Pack1Helper
+ def conflicting_helper
+ "pack1"
+ end
+end
diff --git a/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
new file mode 100644
index 0000000000..cf56697dfb
--- /dev/null
+++ b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
@@ -0,0 +1,5 @@
+module Pack2Helper
+ def conflicting_helper
+ "pack2"
+ end
+end
diff --git a/actionpack/test/fixtures/public/foo/baz.css b/actionpack/test/fixtures/public/foo/baz.css
new file mode 100644
index 0000000000..b5173fbef2
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/baz.css
@@ -0,0 +1,3 @@
+body {
+background: #000;
+}
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index bbb4cc5ef3..82f38b5309 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -214,3 +214,6 @@ class RenderJsonTestException < Exception
return { :error => self.class.name, :message => self.to_s }.to_json
end
end
+
+class Car < Struct.new(:color)
+end
diff --git a/actionpack/test/metal/caching_test.rb b/actionpack/test/metal/caching_test.rb
new file mode 100644
index 0000000000..a2b6763754
--- /dev/null
+++ b/actionpack/test/metal/caching_test.rb
@@ -0,0 +1,32 @@
+require 'abstract_unit'
+
+CACHE_DIR = 'test_cache'
+# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
+FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
+
+class CachingController < ActionController::Metal
+ abstract!
+
+ include ActionController::Caching
+
+ self.page_cache_directory = FILE_STORE_PATH
+ self.cache_store = :file_store, FILE_STORE_PATH
+end
+
+class PageCachingTestController < CachingController
+ caches_page :ok
+
+ def ok
+ self.response_body = "ok"
+ end
+end
+
+class PageCachingTest < ActionController::TestCase
+ tests PageCachingTestController
+
+ def test_should_cache_get_with_ok_status
+ get :ok
+ assert_response :ok
+ assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/ok.html"), "get with ok status should have been cached"
+ end
+end
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 7cc567c72d..bcc55189b9 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -1267,9 +1267,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
-
-
-
def test_caching_stylesheet_include_tag_when_caching_off
ENV["RAILS_ASSET_ID"] = ""
config.perform_caching = false
@@ -1298,6 +1295,17 @@ class AssetTagHelperTest < ActionView::TestCase
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
+
+ def test_caching_stylesheet_include_tag_with_absolute_uri
+ ENV["RAILS_ASSET_ID"] = ""
+
+ assert_dom_equal(
+ %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />),
+ stylesheet_link_tag("/foo/baz", :cache => true)
+ )
+
+ FileUtils.rm(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+ end
end
class AssetTagHelperNonVhostTest < ActionView::TestCase
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 27cc3ad48a..7da293ce23 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -83,6 +83,8 @@ class FormHelperTest < ActionView::TestCase
@post.tags << Tag.new
@blog_post = Blog::Post.new("And his name will be forty and four.", 44)
+
+ @car = Car.new("#000FFF")
end
Routes = ActionDispatch::Routing::RouteSet.new
@@ -610,6 +612,17 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_color_field_with_valid_hex_color_string
+ expected = %{<input id="car_color" name="car[color]" type="color" value="#000fff" />}
+ assert_dom_equal(expected, color_field("car", "color"))
+ end
+
+ def test_color_field_with_invalid_hex_color_string
+ expected = %{<input id="car_color" name="car[color]" type="color" value="#000000" />}
+ @car.color = "#1234TR"
+ assert_dom_equal(expected, color_field("car", "color"))
+ end
+
def test_search_field
expected = %{<input id="contact_notes_query" name="contact[notes_query]" type="search" />}
assert_dom_equal(expected, search_field("contact", "notes_query"))
@@ -631,6 +644,15 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, date_field("post", "written_on"))
end
+ def test_date_field_with_extra_attrs
+ expected = %{<input id="post_written_on" step="2" max="2010-08-15" min="2000-06-15" name="post[written_on]" type="date" value="2004-06-15" />}
+ @post.written_on = DateTime.new(2004, 6, 15)
+ min_value = DateTime.new(2000, 6, 15)
+ max_value = DateTime.new(2010, 8, 15)
+ step = 2
+ assert_dom_equal(expected, date_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
+ end
+
def test_date_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />}
@@ -657,6 +679,15 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, time_field("post", "written_on"))
end
+ def test_time_field_with_extra_attrs
+ expected = %{<input id="post_written_on" step="60" max="10:25:00.000" min="20:45:30.000" name="post[written_on]" type="time" value="01:02:03.000" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ min_value = DateTime.new(2000, 6, 15, 20, 45, 30)
+ max_value = DateTime.new(2010, 8, 15, 10, 25, 00)
+ step = 60
+ assert_dom_equal(expected, time_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
+ end
+
def test_time_field_with_timewithzone_value
previous_time_zone, Time.zone = Time.zone, 'UTC'
expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />}
@@ -672,6 +703,146 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal(expected, time_field("post", "written_on"))
end
+ def test_datetime_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T00:00:00.000+0000" />}
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
+
+ def test_datetime_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
+
+ def test_datetime_field_with_extra_attrs
+ expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00.000+0000" min="2000-06-15T20:45:30.000+0000" name="post[written_on]" type="datetime" value="2004-06-15T01:02:03.000+0000" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ min_value = DateTime.new(2000, 6, 15, 20, 45, 30)
+ max_value = DateTime.new(2010, 8, 15, 10, 25, 00)
+ step = 60
+ assert_dom_equal(expected, datetime_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
+ end
+
+ def test_datetime_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" value="2004-06-15T15:30:45.000+0000" />}
+ @post.written_on = Time.zone.parse('2004-06-15 15:30:45')
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_datetime_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, datetime_field("post", "written_on"))
+ end
+
+ def test_datetime_local_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T00:00:00" />}
+ assert_dom_equal(expected, datetime_local_field("post", "written_on"))
+ end
+
+ def test_datetime_local_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, datetime_local_field("post", "written_on"))
+ end
+
+ def test_datetime_local_field_with_extra_attrs
+ expected = %{<input id="post_written_on" step="60" max="2010-08-15T10:25:00" min="2000-06-15T20:45:30" name="post[written_on]" type="datetime-local" value="2004-06-15T01:02:03" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ min_value = DateTime.new(2000, 6, 15, 20, 45, 30)
+ max_value = DateTime.new(2010, 8, 15, 10, 25, 00)
+ step = 60
+ assert_dom_equal(expected, datetime_local_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
+ end
+
+ def test_datetime_local_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" value="2004-06-15T15:30:45" />}
+ @post.written_on = Time.zone.parse('2004-06-15 15:30:45')
+ assert_dom_equal(expected, datetime_local_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_datetime_local_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="datetime-local" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, datetime_local_field("post", "written_on"))
+ end
+
+ def test_month_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />}
+ assert_dom_equal(expected, month_field("post", "written_on"))
+ end
+
+ def test_month_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="month" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, month_field("post", "written_on"))
+ end
+
+ def test_month_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, month_field("post", "written_on"))
+ end
+
+ def test_month_field_with_extra_attrs
+ expected = %{<input id="post_written_on" step="2" max="2010-12" min="2000-02" name="post[written_on]" type="month" value="2004-06" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ min_value = DateTime.new(2000, 2, 13)
+ max_value = DateTime.new(2010, 12, 23)
+ step = 2
+ assert_dom_equal(expected, month_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
+ end
+
+ def test_month_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="month" value="2004-06" />}
+ @post.written_on = Time.zone.parse('2004-06-15 15:30:45')
+ assert_dom_equal(expected, month_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_week_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
+ assert_dom_equal(expected, week_field("post", "written_on"))
+ end
+
+ def test_week_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, week_field("post", "written_on"))
+ end
+
+ def test_week_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, week_field("post", "written_on"))
+ end
+
+ def test_week_field_with_extra_attrs
+ expected = %{<input id="post_written_on" step="2" max="2010-W51" min="2000-W06" name="post[written_on]" type="week" value="2004-W24" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ min_value = DateTime.new(2000, 2, 13)
+ max_value = DateTime.new(2010, 12, 23)
+ step = 2
+ assert_dom_equal(expected, week_field("post", "written_on", :min => min_value, :max => max_value, :step => step))
+ end
+
+ def test_week_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="week" value="2004-W24" />}
+ @post.written_on = Time.zone.parse('2004-06-15 15:30:45')
+ assert_dom_equal(expected, week_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
def test_url_field
expected = %{<input id="user_homepage" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))
@@ -883,6 +1054,9 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
concat f.submit('Create post')
concat f.button('Create post')
+ concat f.button {
+ concat content_tag(:span, 'Create post')
+ }
end
expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do
@@ -892,7 +1066,8 @@ class FormHelperTest < ActionView::TestCase
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
"<input name='commit' type='submit' value='Create post' />" +
- "<button name='button' type='submit'>Create post</button>"
+ "<button name='button' type='submit'>Create post</button>" +
+ "<button name='button' type='submit'><span>Create post</span></button>"
end
assert_dom_equal expected, output_buffer
@@ -2188,7 +2363,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_builder_does_not_have_form_for_method
- assert ! ActionView::Helpers::FormBuilder.instance_methods.include?('form_for')
+ assert !ActionView::Helpers::FormBuilder.instance_methods.include?(:form_for)
end
def test_form_for_and_fields_for
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 6574e13558..5d19e3274d 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -444,6 +444,11 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_color_field_tag
+ expected = %{<input id="car" name="car" type="color" />}
+ assert_dom_equal(expected, color_field_tag("car"))
+ end
+
def test_search_field_tag
expected = %{<input id="query" name="query" type="search" />}
assert_dom_equal(expected, search_field_tag("query"))
@@ -464,6 +469,26 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, time_field_tag("cell"))
end
+ def test_datetime_field_tag
+ expected = %{<input id="appointment" name="appointment" type="datetime" />}
+ assert_dom_equal(expected, datetime_field_tag("appointment"))
+ end
+
+ def test_datetime_local_field_tag
+ expected = %{<input id="appointment" name="appointment" type="datetime-local" />}
+ assert_dom_equal(expected, datetime_local_field_tag("appointment"))
+ end
+
+ def test_month_field_tag
+ expected = %{<input id="birthday" name="birthday" type="month" />}
+ assert_dom_equal(expected, month_field_tag("birthday"))
+ end
+
+ def test_week_field_tag
+ expected = %{<input id="birthday" name="birthday" type="week" />}
+ assert_dom_equal(expected, week_field_tag("birthday"))
+ end
+
def test_url_field_tag
expected = %{<input id="homepage" name="homepage" type="url" />}
assert_dom_equal(expected, url_field_tag("homepage"))
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 14ca6d9879..057cb47f53 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -33,6 +33,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
assert_equal("22-555-1212", number_to_phone(225551212))
assert_equal("+45-22-555-1212", number_to_phone(225551212, :country_code => 45))
+ assert_equal '111&lt;script&gt;&lt;/script&gt;111&lt;script&gt;&lt;/script&gt;1111', number_to_phone(1111111111, :delimiter => "<script></script>")
end
def test_number_to_currency
@@ -47,6 +48,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
assert_equal("1,234,567,890.50 - K&#269;", number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
+ assert_equal '$1&lt;script&gt;&lt;/script&gt;01', number_to_currency(1.01, :separator => "<script></script>")
+ assert_equal '$1&lt;script&gt;&lt;/script&gt;000.00', number_to_currency(1000, :delimiter => "<script></script>")
end
def test_number_to_percentage
@@ -58,6 +61,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ','))
assert_equal("1000.000 %", number_to_percentage(1000, :format => "%n %"))
+ assert_equal '1&lt;script&gt;&lt;/script&gt;010%', number_to_percentage(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000.000%', number_to_percentage(1000, :delimiter => "<script></script>")
end
def test_number_with_delimiter
@@ -104,6 +109,8 @@ class NumberHelperTest < ActionView::TestCase
def test_number_with_precision_with_custom_delimiter_and_separator
assert_equal '31,83', number_with_precision(31.825, :precision => 2, :separator => ',')
assert_equal '1.231,83', number_with_precision(1231.825, :precision => 2, :separator => ',', :delimiter => '.')
+ assert_equal '1&lt;script&gt;&lt;/script&gt;010', number_with_precision(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000.000', number_with_precision(1000, :delimiter => "<script></script>")
end
def test_number_with_precision_with_significant_digits
@@ -193,6 +200,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '1.0 KB', number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false)
assert_equal '1.012 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false)
assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0
+ assert_equal '9&lt;script&gt;&lt;/script&gt;86 KB', number_to_human_size(10100, :separator => "<script></script>")
end
def test_number_to_human_size_with_custom_delimiter_and_separator
@@ -253,6 +261,9 @@ class NumberHelperTest < ActionView::TestCase
#Spaces are stripped from the resulting string
assert_equal '4', number_to_human(4, :units => {:unit => "", :ten => 'tens '})
assert_equal '4.5 tens', number_to_human(45, :units => {:unit => "", :ten => ' tens '})
+
+ assert_equal '1&lt;script&gt;&lt;/script&gt;01', number_to_human(1.01, :separator => "<script></script>")
+ assert_equal '100&lt;script&gt;&lt;/script&gt;000 Quadrillion', number_to_human(10**20, :delimiter => "<script></script>")
end
def test_number_to_human_with_custom_format
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index f2ed2ec609..c005f040eb 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -222,6 +222,25 @@ module ActionView
end
end
+ test "is able to use mounted routes" do
+ with_routing do |set|
+ app = Class.new do
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ routes.draw { get "bar", :to => lambda {} }
+
+ def self.call(*)
+ end
+ end
+
+ set.draw { mount app => "/foo", :as => "foo_app" }
+
+ assert_equal '/foo/bar', foo_app.bar_path
+ end
+ end
+
test "named routes can be used from helper included in view" do
with_routing do |set|
set.draw { resources :contents }
diff --git a/actionpack/test/template/testing/null_resolver_test.rb b/actionpack/test/template/testing/null_resolver_test.rb
index 535ad3ab14..55ec36e753 100644
--- a/actionpack/test/template/testing/null_resolver_test.rb
+++ b/actionpack/test/template/testing/null_resolver_test.rb
@@ -6,7 +6,7 @@ class NullResolverTest < ActiveSupport::TestCase
templates = resolver.find_all("path.erb", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
assert_equal 1, templates.size, "expected one template"
assert_equal "Template generated by Null Resolver", templates.first.source
- assert_equal "arbitrary/path.erb", templates.first.virtual_path
+ assert_equal "arbitrary/path.erb", templates.first.virtual_path.to_s
assert_equal [:html], templates.first.formats
end
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index f58e474759..a3ab091c6c 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -60,14 +60,14 @@ class TextHelperTest < ActionView::TestCase
simple_format(text)
assert_equal text_clone, text
end
-
+
def test_simple_format_does_not_modify_the_html_options_hash
options = { :class => "foobar"}
passed_options = options.dup
simple_format("some text", passed_options)
assert_equal options, passed_options
end
-
+
def test_simple_format_does_not_modify_the_options_hash
options = { :wrapper_tag => :div, :sanitize => false }
passed_options = options.dup
@@ -75,19 +75,11 @@ class TextHelperTest < ActionView::TestCase
assert_equal options, passed_options
end
- def test_truncate_should_not_be_html_safe
- assert !truncate("Hello World!", :length => 12).html_safe?
- end
-
def test_truncate
assert_equal "Hello World!", truncate("Hello World!", :length => 12)
assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12)
end
- def test_truncate_should_not_escape_input
- assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12)
- end
-
def test_truncate_should_use_default_length_of_30
str = "This is a string that will go longer then the default truncate length of 30"
assert_equal str[0...27] + "...", truncate(str)
@@ -106,7 +98,7 @@ class TextHelperTest < ActionView::TestCase
assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10)
end
-
+
def test_truncate_does_not_modify_the_options_hash
options = { :length => 10 }
passed_options = options.dup
@@ -114,6 +106,53 @@ class TextHelperTest < ActionView::TestCase
assert_equal options, passed_options
end
+ def test_truncate_with_link_options
+ assert_equal "Here's a long test and I...<a href=\"#\">Continue</a>",
+ truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' }
+ end
+
+ def test_truncate_should_be_html_safe
+ assert truncate("Hello World!", :length => 12).html_safe?
+ end
+
+ def test_truncate_should_escape_the_input
+ assert_equal "Hello &lt;sc...", truncate("Hello <script>code!</script>World!!", :length => 12)
+ end
+
+ def test_truncate_should_not_escape_the_input_with_escape_false
+ assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false)
+ end
+
+ def test_truncate_with_escape_false_should_be_html_safe
+ truncated = truncate("Hello <script>code!</script>World!!", :length => 12, :escape => false)
+ assert truncated.html_safe?
+ end
+
+ def test_truncate_with_block_should_be_html_safe
+ truncated = truncate("Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' }
+ assert truncated.html_safe?
+ end
+
+ def test_truncate_with_block_should_escape_the_input
+ assert_equal "&lt;script&gt;code!&lt;/script&gt;He...<a href=\"#\">Continue</a>",
+ truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27) { link_to 'Continue', '#' }
+ end
+
+ def test_truncate_with_block_should_not_escape_the_input_with_escape_false
+ assert_equal "<script>code!</script>He...<a href=\"#\">Continue</a>",
+ truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' }
+ end
+
+ def test_truncate_with_block_with_escape_false_should_be_html_safe
+ truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", :length => 27, :escape => false) { link_to 'Continue', '#' }
+ assert truncated.html_safe?
+ end
+
+ def test_truncate_with_block_should_escape_the_block
+ assert_equal "Here's a long test and I...&lt;script&gt;alert('foo');&lt;/script&gt;",
+ truncate("Here's a long test and I need a continue to read link", :length => 27) { "<script>alert('foo');</script>" }
+ end
+
def test_highlight_should_be_html_safe
assert highlight("This is a beautiful morning", "beautiful").html_safe?
end
@@ -203,7 +242,7 @@ class TextHelperTest < ActionView::TestCase
highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>')
)
end
-
+
def test_highlight_does_not_modify_the_options_hash
options = { :highlighter => '<b>\1</b>', :sanitize => false }
passed_options = options.dup
@@ -256,7 +295,7 @@ class TextHelperTest < ActionView::TestCase
def test_excerpt_with_utf8
assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8))
end
-
+
def test_excerpt_does_not_modify_the_options_hash
options = { :omission => "[...]",:radius => 5 }
passed_options = options.dup
@@ -271,7 +310,7 @@ class TextHelperTest < ActionView::TestCase
def test_word_wrap_with_extra_newlines
assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15))
end
-
+
def test_word_wrap_does_not_modify_the_options_hash
options = { :line_width => 15 }
passed_options = options.dup
diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb
index 97777ccff0..d496dbb35e 100644
--- a/actionpack/test/template/translation_helper_test.rb
+++ b/actionpack/test/template/translation_helper_test.rb
@@ -111,18 +111,28 @@ class TranslationHelperTest < ActiveSupport::TestCase
def test_translate_with_default_named_html
translation = translate(:'translations.missing', :default => :'translations.hello_html')
assert_equal '<a>Hello World</a>', translation
- assert translation.html_safe?
+ assert_equal true, translation.html_safe?
end
def test_translate_with_two_defaults_named_html
translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html'])
assert_equal '<a>Hello World</a>', translation
- assert translation.html_safe?
+ assert_equal true, translation.html_safe?
end
def test_translate_with_last_default_named_html
translation = translate(:'translations.missing', :default => [:'translations.missing', :'translations.hello_html'])
assert_equal '<a>Hello World</a>', translation
- assert translation.html_safe?
+ assert_equal true, translation.html_safe?
+ end
+
+ def test_translate_with_string_default
+ translation = translate(:'translations.missing', default: 'A Generic String')
+ assert_equal 'A Generic String', translation
+ end
+
+ def test_translate_with_array_of_string_defaults
+ translation = translate(:'translations.missing', default: ['A Generic String', 'Second generic string'])
+ assert_equal 'A Generic String', translation
end
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index fb5b35bac6..62608a727f 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -144,6 +144,13 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_button_to_with_block
+ assert_dom_equal(
+ "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><button type=\"submit\"><span>Hello</span></button></div></form>",
+ button_to("http://www.example.com") { content_tag(:span, 'Hello') }
+ )
+ 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
@@ -270,6 +277,16 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_link_tag_with_block
+ assert_dom_equal '<a href="/"><span>Example site</span></a>',
+ link_to('/') { content_tag(:span, 'Example site') }
+ end
+
+ def test_link_tag_with_block_and_html_options
+ assert_dom_equal '<a class="special" href="/"><span>Example site</span></a>',
+ link_to('/', :class => "special") { content_tag(:span, 'Example site') }
+ end
+
def test_link_tag_using_block_in_erb
out = render_erb %{<%= link_to('/') do %>Example site<% end %>}
assert_equal '<a href="/">Example site</a>', out
@@ -282,6 +299,16 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
+ def test_link_tag_escapes_content
+ assert_dom_equal '<a href="/">Malicious &lt;script&gt;content&lt;/script&gt;</a>',
+ link_to("Malicious <script>content</script>", "/")
+ end
+
+ def test_link_tag_does_not_escape_html_safe_content
+ assert_dom_equal '<a href="/">Malicious <script>content</script></a>',
+ link_to("Malicious <script>content</script>".html_safe, "/")
+ end
+
def test_link_to_unless
assert_equal "Showing", link_to_unless(true, "Showing", url_hash)
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 789cff0673..847ae7f237 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,34 @@
## Rails 4.0.0 (unreleased) ##
+* Changed `AM::Serializers::JSON.include_root_in_json' default value to false.
+ Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578.
+
+ class User < ActiveRecord::Base; end
+
+ class Person
+ include ActiveModel::Model
+ include ActiveModel::AttributeMethods
+ include ActiveModel::Serializers::JSON
+
+ attr_accessor :name, :age
+
+ def attributes
+ instance_values
+ end
+ end
+
+ user.as_json
+ => {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true}
+ # root is not included
+
+ person.as_json
+ => {"name"=>"Francesco", "age"=>22}
+ # root is not included
+
+ *Francesco Rodriguez*
+
+* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell*
+
* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
@@ -8,6 +37,26 @@
* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim*
+* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide.
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* No changes.
+
+## Rails 3.2.5 (Jun 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.4 (May 31, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.3 (March 30, 2012) ##
+
+* No changes.
+
## Rails 3.2.2 (March 1, 2012) ##
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index f2d004fb0a..66f324a1a1 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
s.required_ruby_version = '>= 1.9.3'
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index ded1b752df..f6bacf5ec0 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -30,7 +30,6 @@ module ActiveModel
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
- autoload :Configuration
autoload :Conversion
autoload :Dirty
autoload :EachValidator, 'active_model/validator'
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 99918fdb96..6e93fdd625 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -61,8 +61,7 @@ module ActiveModel
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- extend ActiveModel::Configuration
- config_attribute :attribute_method_matchers
+ class_attribute :attribute_method_matchers, instance_writer: false
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
@@ -196,7 +195,7 @@ module ActiveModel
attribute_method_matchers.each do |matcher|
matcher_new = matcher.method_name(new_name).to_s
matcher_old = matcher.method_name(old_name).to_s
- define_optimized_call self, matcher_new, matcher_old
+ define_proxy_call false, self, matcher_new, matcher_old
end
end
@@ -238,7 +237,7 @@ module ActiveModel
if respond_to?(generate_method, true)
send(generate_method, attr_name)
else
- define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
+ define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
end
end
end
@@ -293,7 +292,7 @@ module ActiveModel
# Define a method `name` in `mod` that dispatches to `send`
# using the given `extra` args. This fallbacks `define_method`
# and `send` if the given names cannot be compiled.
- def define_optimized_call(mod, name, send, *extra) #:nodoc:
+ def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
if name =~ NAME_COMPILABLE_REGEXP
defn = "def #{name}(*args)"
else
@@ -303,7 +302,7 @@ module ActiveModel
extra = (extra.map(&:inspect) << "*args").join(", ")
if send =~ CALL_COMPILABLE_REGEXP
- target = "#{send}(#{extra})"
+ target = "#{"self." unless include_private}#{send}(#{extra})"
else
target = "send(:'#{send}', #{extra})"
end
@@ -315,7 +314,7 @@ module ActiveModel
RUBY
end
- class AttributeMethodMatcher
+ class AttributeMethodMatcher #:nodoc:
attr_reader :prefix, :suffix, :method_missing_target
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
deleted file mode 100644
index ba5a6a2075..0000000000
--- a/activemodel/lib/active_model/configuration.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'active_support/concern'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
-
-module ActiveModel
- # This API is for Rails' internal use and is not currently considered 'public', so
- # it may change in the future without warning.
- #
- # It creates configuration attributes that can be inherited from a module down
- # to a class that includes the module. E.g.
- #
- # module MyModel
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- #
- # class Post
- # include MyModel
- # end
- #
- # Post.awesome # => true
- #
- # Post.awesome = false
- # Post.awesome # => false
- # MyModel.awesome # => true
- #
- # We assume that the module will have a ClassMethods submodule containing methods
- # to be transferred to the including class' singleton class.
- #
- # Config options can also be defined directly on a class:
- #
- # class Post
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # end
- #
- # So this allows us to define a module that doesn't care about whether it is being
- # included in a class or a module:
- #
- # module Awesomeness
- # extend ActiveSupport::Concern
- #
- # included do
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- # end
- #
- # class Post
- # include Awesomeness
- # end
- #
- # module AwesomeModel
- # include Awesomeness
- # end
- module Configuration #:nodoc:
- def config_attribute(name, options = {})
- klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute
- klass.new(self, name, options).define
- end
-
- class Attribute
- attr_reader :host, :name, :options
-
- def initialize(host, name, options)
- @host, @name, @options = host, name, options
- end
-
- def instance_writer?
- options.fetch(:instance_writer, false)
- end
- end
-
- class ClassAttribute < Attribute
- def define
- if options[:global]
- host.cattr_accessor name, :instance_writer => instance_writer?
- else
- host.class_attribute name, :instance_writer => instance_writer?
- end
- end
- end
-
- class ModuleAttribute < Attribute
- def class_methods
- @class_methods ||= begin
- if host.const_defined?(:ClassMethods, false)
- host.const_get(:ClassMethods)
- else
- host.const_set(:ClassMethods, Module.new)
- end
- end
- end
-
- def define
- host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- attr_accessor :#{name}
- def #{name}?; !!#{name}; end
- CODE
-
- name, host = self.name, self.host
-
- class_methods.class_eval do
- define_method(name) { host.send(name) }
- define_method("#{name}?") { !!send(name) }
- end
-
- host.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
- def #{name}?; !!#{name}; end
- CODE
-
- if options[:global]
- class_methods.class_eval do
- define_method("#{name}=") { |val| host.send("#{name}=", val) }
- end
- else
- class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}=(val)
- singleton_class.class_eval do
- remove_possible_method(:#{name})
- define_method(:#{name}) { val }
- end
- end
- CODE
- end
-
- host.send(:attr_writer, name) if instance_writer?
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 3f2fd12db7..7014d8114f 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -11,14 +11,14 @@ module ActiveModel
#
# The requirements for implementing ActiveModel::Dirty are:
#
- # * <tt>include ActiveModel::Dirty</tt> in your object
+ # * <tt>include ActiveModel::Dirty</tt> in your object.
# * Call <tt>define_attribute_methods</tt> passing each method you want to
- # track
+ # track.
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
- # attribute
+ # attribute.
#
# If you wish to also track previous changes on save or update, you need to
- # add
+ # add:
#
# @previously_changed = changes
#
@@ -27,7 +27,6 @@ module ActiveModel
# A minimal implementation could be:
#
# class Person
- #
# include ActiveModel::Dirty
#
# define_attribute_methods :name
@@ -45,47 +44,49 @@ module ActiveModel
# @previously_changed = changes
# @changed_attributes.clear
# end
- #
# end
#
- # == Examples:
- #
# A newly instantiated object is unchanged:
+ #
# person = Person.find_by_name('Uncle Bob')
# person.changed? # => false
#
# Change the name:
+ #
# person.name = 'Bob'
# person.changed? # => true
# person.name_changed? # => true
- # person.name_was # => 'Uncle Bob'
- # person.name_change # => ['Uncle Bob', 'Bob']
+ # person.name_was # => "Uncle Bob"
+ # person.name_change # => ["Uncle Bob", "Bob"]
# person.name = 'Bill'
- # person.name_change # => ['Uncle Bob', 'Bill']
+ # person.name_change # => ["Uncle Bob", "Bill"]
#
# Save the changes:
+ #
# person.save
# person.changed? # => false
# person.name_changed? # => false
#
# Assigning the same value leaves the attribute unchanged:
+ #
# person.name = 'Bill'
# person.name_changed? # => false
# person.name_change # => nil
#
# Which attributes have changed?
+ #
# person.name = 'Bob'
- # person.changed # => ['name']
- # person.changes # => { 'name' => ['Bill', 'Bob'] }
+ # person.changed # => ["name"]
+ # person.changes # => {"name" => ["Bill", "Bob"]}
#
# If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
# to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
# in-place attributes.
#
# person.name_will_change!
- # person.name_change # => ['Bill', 'Bill']
+ # person.name_change # => ["Bill", "Bill"]
# person.name << 'y'
- # person.name_change # => ['Bill', 'Billy']
+ # person.name_change # => ["Bill", "Billy"]
module Dirty
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
@@ -95,7 +96,8 @@ module ActiveModel
attribute_method_affix :prefix => 'reset_', :suffix => '!'
end
- # Returns true if any attribute have unsaved changes, false otherwise.
+ # Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
+ #
# person.changed? # => false
# person.name = 'bob'
# person.changed? # => true
@@ -103,32 +105,41 @@ module ActiveModel
changed_attributes.present?
end
- # List of attributes with unsaved changes.
+ # Returns an array with the name of the attributes with unsaved changes.
+ #
# person.changed # => []
# person.name = 'bob'
- # person.changed # => ['name']
+ # person.changed # => ["name"]
def changed
changed_attributes.keys
end
- # Map of changed attrs => [original value, new value].
+ # Returns a hash of changed attributes indicating their original
+ # and new values like <tt>attr => [original value, new value]</tt>.
+ #
# person.changes # => {}
# person.name = 'bob'
- # person.changes # => { 'name' => ['bill', 'bob'] }
+ # person.changes # => { "name" => ["bill", "bob"] }
def changes
HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
end
- # Map of attributes that were changed when the model was saved.
- # person.name # => 'bob'
+ # Returns a hash of attributes that were changed before the model was saved.
+ #
+ # person.name # => "bob"
# person.name = 'robert'
# person.save
- # person.previous_changes # => {'name' => ['bob, 'robert']}
+ # person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
@previously_changed
end
- # Map of change <tt>attr => original value</tt>.
+ # Returns a hash of the attributes with unsaved changes indicating their original
+ # values like <tt>attr => original value</tt>.
+ #
+ # person.name # => "bob"
+ # person.name = 'robert'
+ # person.changed_attributes # => {"name" => "bob"}
def changed_attributes
@changed_attributes ||= {}
end
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 893fbf92c3..8f2c0bf00a 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -9,13 +9,11 @@ module ActiveModel
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
+ class_attribute :_accessible_attributes, instance_writer: false
+ class_attribute :_protected_attributes, instance_writer: false
+ class_attribute :_active_authorizer, instance_writer: false
- config_attribute :_accessible_attributes
- config_attribute :_protected_attributes
- config_attribute :_active_authorizer
-
- config_attribute :_mass_assignment_sanitizer
+ class_attribute :_mass_assignment_sanitizer, instance_writer: false
self.mass_assignment_sanitizer = :logger
end
@@ -62,7 +60,7 @@ module ActiveModel
# Attributes named in this macro are protected from mass-assignment
# whenever attributes are sanitized before assignment. A role for the
# attributes is optional, if no role is provided then :default is used.
- # A role can be defined by using the :as option.
+ # A role can be defined by using the :as option with a symbol or an array of symbols as the value.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
@@ -128,7 +126,7 @@ module ActiveModel
#
# Like +attr_protected+, a role for the attributes is optional,
# if no role is provided then :default is used. A role can be defined by
- # using the :as option.
+ # using the :as option with a symbol or an array of symbols as the value.
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
# will only set attributes in this list, to assign to the rest of
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 9661349503..415ab0ad17 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -5,7 +5,7 @@ module ActiveModel
class PermissionSet < Set
def +(values)
- super(values.map(&:to_s))
+ super(values.compact.map(&:to_s))
end
def include?(key)
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
index 3d463885be..8de6918d18 100644
--- a/activemodel/lib/active_model/observer_array.rb
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -17,6 +17,11 @@ module ActiveModel
# Disables one or more observers. This supports multiple forms:
#
+ # ORM.observers.disable :all
+ # # => disables all observers for all models subclassed from
+ # # an ORM base class that includes ActiveModel::Observing
+ # # e.g. ActiveRecord::Base
+ #
# ORM.observers.disable :user_observer
# # => disables the UserObserver
#
@@ -27,9 +32,6 @@ module ActiveModel
# ORM.observers.disable :observer_1, :observer_2
# # => disables Observer1 and Observer2 for all models.
#
- # ORM.observers.disable :all
- # # => disables all observers for all models.
- #
# User.observers.disable :all do
# # all user observers are disabled for
# # just the duration of the block
@@ -40,6 +42,11 @@ module ActiveModel
# Enables one or more observers. This supports multiple forms:
#
+ # ORM.observers.enable :all
+ # # => enables all observers for all models subclassed from
+ # # an ORM base class that includes ActiveModel::Observing
+ # # e.g. ActiveRecord::Base
+ #
# ORM.observers.enable :user_observer
# # => enables the UserObserver
#
@@ -51,9 +58,6 @@ module ActiveModel
# ORM.observers.enable :observer_1, :observer_2
# # => enables Observer1 and Observer2 for all models.
#
- # ORM.observers.enable :all
- # # => enables all observers for all models.
- #
# User.observers.enable :all do
# # all user observers are enabled for
# # just the duration of the block
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 63ab8e7edc..b4baf3a946 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,83 +10,82 @@ module ActiveModel
included do
extend ActiveModel::Naming
- extend ActiveModel::Configuration
- config_attribute :include_root_in_json
- self.include_root_in_json = true
+ class_attribute :include_root_in_json
+ self.include_root_in_json = false
end
# Returns a hash representing the model. Some configuration can be
# passed through +options+.
#
# The option <tt>include_root_in_json</tt> controls the top-level behavior
- # of +as_json+. If true (the default) +as_json+ will emit a single root
- # node named after the object's type. For example:
+ # of +as_json+. If true +as_json+ will emit a single root node named after
+ # the object's type. The default value for <tt>include_root_in_json</tt>
+ # option is +false+.
#
# user = User.find(1)
# user.as_json
- # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true} }
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true}
+ #
+ # ActiveRecord::Base.include_root_in_json = true
#
- # ActiveRecord::Base.include_root_in_json = false
# user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
+ # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true } }
#
- # This behavior can also be achieved by setting the <tt>:root</tt> option to +false+ as in:
+ # This behavior can also be achieved by setting the <tt>:root</tt> option
+ # to +true+ as in:
#
# user = User.find(1)
- # user.as_json(root: false)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
- #
- # The remainder of the examples in this section assume include_root_in_json is set to
- # <tt>false</tt>.
+ # user.as_json(root: true)
+ # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true } }
#
# Without any +options+, the returned Hash will include all the model's
- # attributes. For example:
+ # attributes.
#
# user = User.find(1)
# user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true}
#
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
- # included, and work similar to the +attributes+ method. For example:
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
+ # the attributes included, and work similar to the +attributes+ method.
#
- # user.as_json(:only => [ :id, :name ])
- # # => {"id": 1, "name": "Konata Izumi"}
+ # user.as_json(only: [:id, :name])
+ # # => { "id" => 1, "name" => "Konata Izumi" }
#
- # user.as_json(:except => [ :id, :created_at, :age ])
- # # => {"name": "Konata Izumi", "awesome": true}
+ # user.as_json(except: [:id, :created_at, :age])
+ # # => { "name" => "Konata Izumi", "awesome" => true }
#
# To include the result of some method calls on the model use <tt>:methods</tt>:
#
- # user.as_json(:methods => :permalink)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "permalink": "1-konata-izumi"}
+ # user.as_json(methods: :permalink)
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "permalink" => "1-konata-izumi" }
#
# To include associations use <tt>:include</tt>:
#
- # user.as_json(:include => :posts)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
- # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
+ # user.as_json(include: :posts)
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
+ # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
#
# Second level and higher order associations work as well:
#
- # user.as_json(:include => { :posts => {
- # :include => { :comments => {
- # :only => :body } },
- # :only => :title } })
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
- # "title": "Welcome to the weblog"},
- # {"comments": [{"body": "Don't think too hard"}],
- # "title": "So I was thinking"}]}
+ # user.as_json(include: { posts: {
+ # include: { comments: {
+ # only: :body } },
+ # only: :title } })
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
+ # # "title" => "Welcome to the weblog" },
+ # # { "comments" => [ { "body" => "Don't think too hard" } ],
+ # # "title" => "So I was thinking" } ] }
def as_json(options = nil)
root = include_root_in_json
root = options[:root] if options.try(:key?, :root)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index b78f1ff3f3..2b3e9ce134 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -172,7 +172,7 @@ module ActiveModel
# <id type="integer">1</id>
# <name>David</name>
# <age type="integer">16</age>
- # <created-at type="datetime">2011-01-30T22:29:23Z</created-at>
+ # <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
# </user>
#
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 6f0ca92e2a..7a86701f73 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -42,9 +42,9 @@ module ActiveModel
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
options = { :count => 1 }.merge!(options)
- parts = attribute.to_s.split(".", 2)
+ parts = attribute.to_s.split(".")
attribute = parts.pop
- namespace = parts.pop
+ namespace = parts.join("/") unless parts.empty?
attributes_scope = "#{self.i18n_scope}.attributes"
if namespace
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 611e9ffd55..06eebf79d9 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -52,8 +52,7 @@ module ActiveModel
attr_accessor :validation_context
define_callbacks :validate, :scope => :name
- extend ActiveModel::Configuration
- config_attribute :_validators
+ class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 4f09679541..edd42d85f2 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -30,8 +30,7 @@ module ActiveModel
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't be
# part of. This can be supplied as a proc or lambda which returns an
# enumerable. If the enumerable is a range the test is performed with
- # <tt>Range#cover?</tt> (backported in Active Support for 1.8), otherwise
- # with <tt>include?</tt>.
+ # <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# reserved").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index dd87e312f9..ffdf842d94 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -32,11 +32,21 @@ module ActiveModel
def record_error(record, attribute, name, value)
record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
end
-
+
+ def regexp_using_multiline_anchors?(regexp)
+ regexp.source.start_with?("^") ||
+ (regexp.source.end_with?("$") && !regexp.source.end_with?("\\$"))
+ end
+
def check_options_validity(options, name)
option = options[name]
if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
+ elsif option && option.is_a?(Regexp) &&
+ regexp_using_multiline_anchors?(option) && options[:multiline] != true
+ raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
+ "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
+ ":multiline => true option?"
end
end
end
@@ -47,7 +57,7 @@ module ActiveModel
# attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
- # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
+ # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :on => :create
# end
#
# Alternatively, you can require that the specified attribute does _not_
@@ -63,12 +73,16 @@ module ActiveModel
# class Person < ActiveRecord::Base
# # Admin can have number as a first letter in their screen name
# validates_format_of :screen_name,
- # :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\Z/i : /\A[a-z][a-z0-9_\-]*\Z/i }
+ # :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
# end
#
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
+ # Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass the
+ # :multiline => true option in case you use any of these two anchors in the provided
+ # regular expression. In most cases, you should be using <tt>\A</tt> and <tt>\z</tt>.
+ #
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
# In addition, both must be a regular expression or a proc or lambda, or
# else an exception will be raised.
@@ -98,6 +112,9 @@ module ActiveModel
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ # * <tt>:multiline</tt> - Set to true if your regular expression contains
+ # anchors that match the beginning or end of lines as opposed to the
+ # beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index ffdbed0fc1..8810f2a3c1 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -28,8 +28,8 @@ module ActiveModel
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items. This can be
# supplied as a proc or lambda which returns an enumerable. If the enumerable
- # is a range the test is performed with <tt>Range#cover?</tt>
- # (backported in Active Support for 1.8), otherwise with <tt>include?</tt>.
+ # is a range the test is performed with <tt>Range#cover?</tt>, otherwise with
+ # <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is not
# included in the list").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index d94c4e3f4f..6c13d2b4a2 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -88,6 +88,7 @@ module ActiveModel
defaults.merge!(:attributes => attributes)
validations.each do |key, options|
+ next unless options
key = "#{key.to_s.camelize}Validator"
begin
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index a9db29ee21..e2f2cecc09 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -76,6 +76,19 @@ private
end
end
+class ModelWithRubyKeywordNamedAttributes
+ include ActiveModel::AttributeMethods
+
+ def attributes
+ { :begin => 'value of begin', :end => 'value of end' }
+ end
+
+private
+ def attribute(name)
+ attributes[name.to_sym]
+ end
+end
+
class ModelWithoutAttributesMethod
include ActiveModel::AttributeMethods
end
@@ -155,6 +168,15 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
end
+ test '#alias_attribute works with attributes named as a ruby keyword' do
+ ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end])
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin)
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end)
+
+ assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from
+ assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to
+ end
+
test '#undefine_attribute_methods removes attribute methods' do
ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods
diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb
deleted file mode 100644
index a172fa26a3..0000000000
--- a/activemodel/test/cases/configuration_test.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-require 'cases/helper'
-
-class ConfigurationOnModuleTest < ActiveModel::TestCase
- def setup
- @mod = mod = Module.new do
- extend ActiveSupport::Concern
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :boolean
-
- config_attribute :lol, instance_writer: true
- end
-
- @klass = Class.new do
- include mod
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "default" do
- assert_equal "default", @mod.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.new.omg
- end
-
- test "setting" do
- @mod.omg = "lol"
- assert_equal "lol", @mod.omg
- end
-
- test "setting on class including the module" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @klass.new.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on subclass of class including the module" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on instance" do
- assert !@klass.new.respond_to?(:omg=)
-
- @klass.lol = "lol"
- obj = @klass.new
- assert_equal "lol", obj.lol
- obj.lol = "omg"
- assert_equal "omg", obj.lol
- assert_equal "lol", @klass.lol
- assert_equal "lol", @klass.new.lol
- obj.lol = false
- assert !obj.lol?
- end
-
- test "global attribute" do
- assert_equal "default", @mod.wtf
- assert_equal "default", @klass.wtf
-
- @mod.wtf = "wtf"
-
- assert_equal "wtf", @mod.wtf
- assert_equal "wtf", @klass.wtf
-
- @klass.wtf = "lol"
-
- assert_equal "lol", @mod.wtf
- assert_equal "lol", @klass.wtf
- end
-
- test "boolean" do
- assert_equal false, @mod.boolean?
- assert_equal false, @klass.new.boolean?
- @mod.boolean = true
- assert_equal true, @mod.boolean?
- assert_equal true, @klass.new.boolean?
- end
-end
-
-class ConfigurationOnClassTest < ActiveModel::TestCase
- def setup
- @klass = Class.new do
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :omg2, instance_writer: true
- config_attribute :wtf2, instance_writer: true, global: true
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "defaults" do
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.wtf
- assert_equal "default", @subklass.omg
- assert_equal "default", @subklass.wtf
- end
-
- test "changing" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @subklass.omg
- end
-
- test "changing in subclass" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- end
-
- test "changing global" do
- @klass.wtf = "wtf"
- assert_equal "wtf", @klass.wtf
- assert_equal "wtf", @subklass.wtf
-
- @subklass.wtf = "lol"
- assert_equal "lol", @klass.wtf
- assert_equal "lol", @subklass.wtf
- end
-
- test "instance_writer" do
- obj = @klass.new
-
- @klass.omg2 = "omg"
- @klass.wtf2 = "wtf"
-
- assert_equal "omg", obj.omg2
- assert_equal "wtf", obj.wtf2
-
- obj.omg2 = "lol"
- obj.wtf2 = "lol"
-
- assert_equal "lol", obj.omg2
- assert_equal "lol", obj.wtf2
- assert_equal "omg", @klass.omg2
- assert_equal "lol", @klass.wtf2
- end
-end
diff --git a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
index d005b638e4..8082c49852 100644
--- a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
@@ -13,6 +13,12 @@ class PermissionSetTest < ActiveModel::TestCase
assert new_list.include?('admin'), "did not add collection to #{@permission_list.inspect}}"
end
+ test "+ compacts added collection values" do
+ added_collection = [ nil ]
+ new_list = @permission_list + added_collection
+ assert_equal new_list, @permission_list, "did not add collection to #{@permission_list.inspect}}"
+ end
+
test "include? normalizes multi-parameter keys" do
multi_param_key = 'admin(1)'
new_list = @permission_list += [ 'admin' ]
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 7160635eb4..e2690f1827 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -31,10 +31,15 @@ class JsonSerializationTest < ActiveModel::TestCase
@contact.preferences = { 'shows' => 'anime' }
end
- test "should include root in json" do
+ def teardown
+ # set to the default value
+ Contact.include_root_in_json = false
+ end
+
+ test "should not include root in json (class method)" do
json = @contact.to_json
- assert_match %r{^\{"contact":\{}, json
+ assert_no_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
@@ -42,41 +47,31 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
- test "should not include root in json (class method)" do
- begin
- Contact.include_root_in_json = false
- json = @contact.to_json
-
- assert_no_match %r{^\{"contact":\{}, json
- assert_match %r{"name":"Konata Izumi"}, json
- assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
- assert_match %r{"awesome":true}, json
- assert_match %r{"preferences":\{"shows":"anime"\}}, json
- ensure
- Contact.include_root_in_json = true
- end
+ test "should include root in json if include_root_in_json is true" do
+ Contact.include_root_in_json = true
+ json = @contact.to_json
+
+ assert_match %r{^\{"contact":\{}, json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"age":16}, json
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome":true}, json
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
test "should include root in json (option) even if the default is set to false" do
- begin
- Contact.include_root_in_json = false
- json = @contact.to_json(:root => true)
- assert_match %r{^\{"contact":\{}, json
- ensure
- Contact.include_root_in_json = true
- end
+ json = @contact.to_json(root: true)
+ assert_match %r{^\{"contact":\{}, json
end
test "should not include root in json (option)" do
-
- json = @contact.to_json(:root => false)
+ json = @contact.to_json(root: false)
assert_no_match %r{^\{"contact":\{}, json
end
test "should include custom root in json" do
- json = @contact.to_json(:root => 'json_contact')
+ json = @contact.to_json(root: 'json_contact')
assert_match %r{^\{"json_contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
@@ -107,7 +102,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "should allow attribute filtering with except" do
- json = @contact.to_json(:except => [:name, :age])
+ json = @contact.to_json(except: [:name, :age])
assert_no_match %r{"name":"Konata Izumi"}, json
assert_no_match %r{"age":16}, json
@@ -122,10 +117,10 @@ class JsonSerializationTest < ActiveModel::TestCase
def @contact.favorite_quote; "Constraints are liberating"; end
# Single method.
- assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label)
+ assert_match %r{"label":"Has cheezburger"}, @contact.to_json(only: :name, methods: :label)
# Both methods.
- methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
+ methods_json = @contact.to_json(only: :name, methods: [:label, :favorite_quote])
assert_match %r{"label":"Has cheezburger"}, methods_json
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
@@ -143,14 +138,15 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "serializable_hash should not modify options passed in argument" do
- options = { :except => :name }
+ options = { except: :name }
@contact.serializable_hash(options)
assert_nil options[:only]
assert_equal :name, options[:except]
end
- test "as_json should return a hash" do
+ test "as_json should return a hash if include_root_in_json is true" do
+ Contact.include_root_in_json = true
json = @contact.as_json
assert_kind_of Hash, json
@@ -160,7 +156,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
end
- test "from_json should set the object's attributes" do
+ test "from_json should work without a root (class attribute)" do
json = @contact.to_json
result = Contact.new.from_json(json)
@@ -172,7 +168,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "from_json should work without a root (method parameter)" do
- json = @contact.to_json(:root => false)
+ json = @contact.to_json
result = Contact.new.from_json(json, false)
assert_equal result.name, @contact.name
@@ -182,24 +178,19 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_equal result.preferences, @contact.preferences
end
- test "from_json should work without a root (class attribute)" do
- begin
- Contact.include_root_in_json = false
- json = @contact.to_json
- result = Contact.new.from_json(json)
-
- assert_equal result.name, @contact.name
- assert_equal result.age, @contact.age
- assert_equal Time.parse(result.created_at), @contact.created_at
- assert_equal result.awesome, @contact.awesome
- assert_equal result.preferences, @contact.preferences
- ensure
- Contact.include_root_in_json = true
- end
+ test "from_json should work with a root (method parameter)" do
+ json = @contact.to_json(root: :true)
+ result = Contact.new.from_json(json, true)
+
+ assert_equal result.name, @contact.name
+ assert_equal result.age, @contact.age
+ assert_equal Time.parse(result.created_at), @contact.created_at
+ assert_equal result.awesome, @contact.awesome
+ assert_equal result.preferences, @contact.preferences
end
test "custom as_json should be honored when generating json" do
- def @contact.as_json(options); { :name => name, :created_at => created_at }; end
+ def @contact.as_json(options); { name: name, created_at: created_at }; end
json = @contact.to_json
assert_match %r{"name":"Konata Izumi"}, json
@@ -209,7 +200,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "custom as_json options should be extendible" do
- def @contact.as_json(options = {}); super(options.merge(:only => [:name])); end
+ def @contact.as_json(options = {}); super(options.merge(only: [:name])); end
json = @contact.to_json
assert_match %r{"name":"Konata Izumi"}, json
@@ -217,5 +208,4 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{"awesome":}, json
assert_no_match %r{"preferences":}, json
end
-
end
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 5fa227e0e0..7eb48abc3c 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -140,7 +140,7 @@ class XmlSerializationTest < ActiveModel::TestCase
end
test "should serialize datetime" do
- assert_match %r{<created-at type=\"datetime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
+ assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
end
test "should serialize boolean" do
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 4999583802..fd833cdd06 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -56,6 +56,11 @@ class ActiveModelI18nTests < ActiveModel::TestCase
assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute')
end
+ def test_translated_deeply_nested_model_attributes
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/contacts/addresses" => {:street => 'Deeply Nested Address Street'}}}
+ assert_equal 'Deeply Nested Address Street', Person.human_attribute_name('contacts.addresses.street')
+ end
+
def test_translated_nested_model_attributes
I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/addresses" => {:street => 'Person Address Street'}}}
assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street')
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 41a1131bcb..308a3c6cef 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -11,7 +11,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format
- Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
+ Topic.validates_format_of(:title, :content, :with => /\AValidation\smacros \w+!\z/, :message => "is bad data")
t = Topic.new("title" => "i'm incorrect", "content" => "Validation macros rule!")
assert t.invalid?, "Shouldn't be valid"
@@ -27,7 +27,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_with_allow_blank
- Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank => true)
+ Topic.validates_format_of(:title, :with => /\AValidation\smacros \w+!\z/, :allow_blank => true)
assert Topic.new("title" => "Shouldn't be valid").invalid?
assert Topic.new("title" => "").valid?
assert Topic.new("title" => nil).valid?
@@ -36,7 +36,7 @@ class PresenceValidationTest < ActiveModel::TestCase
# testing ticket #3142
def test_validate_format_numeric
- Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
+ Topic.validates_format_of(:title, :content, :with => /\A[1-9][0-9]*\z/, :message => "is bad data")
t = Topic.new("title" => "72x", "content" => "6789")
assert t.invalid?, "Shouldn't be valid"
@@ -63,11 +63,21 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_with_formatted_message
- Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %{value}")
+ Topic.validates_format_of(:title, :with => /\AValid Title\z/, :message => "can't be %{value}")
t = Topic.new(:title => 'Invalid title')
assert t.invalid?
assert_equal ["can't be Invalid title"], t.errors[:title]
end
+
+ def test_validate_format_of_with_multiline_regexp_should_raise_error
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => /^Valid Title$/) }
+ end
+
+ def test_validate_format_of_with_multiline_regexp_and_option
+ assert_nothing_raised(ArgumentError) do
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :multiline => true)
+ end
+ end
def test_validate_format_with_not_option
Topic.validates_format_of(:title, :without => /foo/, :message => "should not contain foo")
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 6b6aad3bd1..bb751cf9c5 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -141,7 +141,7 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_format_of on generated message #{name}" do
- Person.validates_format_of :title, validation_options.merge(:with => /^[1-9][0-9]*$/)
+ Person.validates_format_of :title, validation_options.merge(:with => /\A[1-9][0-9]*\z/)
@person.title = '72x'
@person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(:value => '72x'))
@person.valid?
@@ -291,7 +291,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_format_of w/o mocha
set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge|
- Person.validates_format_of :title, options_to_merge.merge(:with => /^[1-9][0-9]*$/)
+ Person.validates_format_of :title, options_to_merge.merge(:with => /\A[1-9][0-9]*\z/)
end
# validates_inclusion_of w/o mocha
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 1f5023bf76..8ea9745fbf 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -330,6 +330,11 @@ class ValidationsTest < ActiveModel::TestCase
end
end
+ def test_validates_with_false_hash_value
+ Topic.validates :title, :presence => false
+ assert Topic.new.valid?
+ end
+
def test_strict_validation_error_message
Topic.validates :title, :strict => true, :presence => true
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 8f1f315e42..10bfe1945a 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,82 @@
## Rails 4.0.0 (unreleased) ##
+* Added `stored_attributes` hash which contains the attributes stored using
+ ActiveRecord::Store. This allows you to retrieve the list of attributes
+ you've defined.
+
+ class User < ActiveRecord::Base
+ store :settings, accessors: [:color, :homepage]
+ end
+
+ User.stored_attributes[:settings] # [:color, :homepage]
+
+ *Joost Baaij & Carlos Antonio da Silva*
+
+* `composed_of` was removed. You'll have to write your own accessor
+ and mutator methods if you'd like to use value objects to represent some
+ portion of your models. So, instead of:
+
+ class Person < ActiveRecord::Base
+ composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
+ end
+
+ you could write something like this:
+
+ def address
+ @address ||= Address.new(address_street, address_city)
+ end
+
+ def address=(address)
+ self[:address_street] = @address.street
+ self[:address_city] = @address.city
+
+ @address = address
+ end
+
+ *Steve Klabnik*
+
+* PostgreSQL default log level is now 'warning', to bypass the noisy notice
+ messages. You can change the log level using the `min_messages` option
+ available in your config/database.yml.
+
+ *kennyj*
+
+* Add uuid datatype support to PostgreSQL adapter. *Konstantin Shabanov*
+
+* `update_attribute` has been removed. Use `update_column` if
+ you want to bypass mass-assignment protection, validations, callbacks,
+ and touching of updated_at. Otherwise please use `update_attributes`.
+
+ *Steve Klabnik*
+
+* Added `ActiveRecord::Migration.check_pending!` that raises an error if
+ migrations are pending. *Richard Schneeman*
+
+* Added `#destroy!` which acts like `#destroy` but will raise an
+ `ActiveRecord::RecordNotDestroyed` exception instead of returning `false`.
+
+ *Marc-André Lafortune*
+
+* Allow blocks for `count` with `ActiveRecord::Relation`, to work similar as
+ `Array#count`:
+
+ Person.where("age > 26").count { |person| person.gender == 'female' }
+
+ *Chris Finne & Carlos Antonio da Silva*
+
+* Added support to `CollectionAssociation#delete` for passing `fixnum`
+ or `string` values as record ids. This finds the records responding
+ to the `id` and executes delete on them.
+
+ class Person < ActiveRecord::Base
+ has_many :pets
+ end
+
+ person.pets.delete("1") # => [#<Pet id: 1>]
+ person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
+
+ *Francesco Rodriguez*
+
* Deprecated most of the 'dynamic finder' methods. All dynamic methods
except for `find_by_...` and `find_by_...!` are deprecated. Here's
how you can rewrite the code:
@@ -324,6 +401,33 @@
* PostgreSQL hstore types are automatically deserialized from the database.
+## Rails 3.2.5 (Jun 1, 2012) ##
+
+* Restore behavior of Active Record 3.2.3 scopes.
+ A series of commits relating to preloading and scopes caused a regression.
+
+ *Andrew White*
+
+
+## Rails 3.2.4 (May 31, 2012) ##
+
+* Perf fix: Don't load the records when doing assoc.delete_all.
+ GH #6289. *Jon Leighton*
+
+* Association preloading shouldn't be affected by the current scoping.
+ This could cause infinite recursion and potentially other problems.
+ See GH #5667. *Jon Leighton*
+
+* Datetime attributes are forced to be changed. GH #3965
+
+* Fix attribute casting. GH #5549
+
+* Fix #5667. Preloading should ignore scoping.
+
+* Predicate builder should not recurse for determining where columns.
+ Thanks to Ben Murphy for reporting this! CVE-2012-2661
+
+
## Rails 3.2.3 (March 30, 2012) ##
* Added find_or_create_by_{attribute}! dynamic method. *Andrew White*
@@ -1159,8 +1263,6 @@
## Rails 3.0.0 (August 29, 2010) ##
-* Changed update_attribute to not run callbacks and update the record directly in the database *Neeraj Singh*
-
* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope *José Valim*
* New rake task, db:migrate:status, displays status of migrations #4947 *Kevin Skoglund*
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 30a66ff5f0..60965590a1 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -46,25 +46,13 @@ A short rundown of some of the major features:
{Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
-* Aggregations of value objects.
-
- class Account < ActiveRecord::Base
- composed_of :balance, :class_name => "Money",
- :mapping => %w(balance amount)
- composed_of :address,
- :mapping => [%w(address_street street), %w(address_city city)]
- end
-
- {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
-
-
* Validation rules that can differ for new or existing objects.
class Account < ActiveRecord::Base
- validates_presence_of :subdomain, :name, :email_address, :password
- validates_uniqueness_of :subdomain
- validates_acceptance_of :terms_of_service, :on => :create
- validates_confirmation_of :password, :email_address, :on => :create
+ validates :subdomain, :name, :email_address, :password, presence: true
+ validates :subdomain, uniqueness: true
+ validates :terms_of_service, acceptance: true, on: :create
+ validates :password, :email_address, confirmation: true, on: :create
end
{Learn more}[link:classes/ActiveRecord/Validations.html]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 7feb0b75a0..a29d7b0e99 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -114,6 +114,16 @@ namespace :postgresql do
config = ARTest.config['connections']['postgresql']
%x( createdb -E UTF8 #{config['arunit']['database']} )
%x( createdb -E UTF8 #{config['arunit2']['database']} )
+
+ # prepare hstore
+ version = %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2")
+ %w(arunit arunit2).each do |db|
+ if version < "9.1.0"
+ puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html"
+ else
+ %x( psql #{config[db]['database']} -c "CREATE EXTENSION hstore;" )
+ end
+ end
end
desc 'Drop the PostgreSQL test databases'
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index e8e5f4adfe..dca7f13fd2 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.'
s.required_ruby_version = '>= 1.9.3'
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 210820062b..a894d83ea7 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -36,7 +36,6 @@ module ActiveRecord
autoload :ConnectionNotEstablished, 'active_record/errors'
autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
- autoload :Aggregations
autoload :Associations
autoload :AttributeMethods
autoload :AttributeAssignment
@@ -80,6 +79,7 @@ module ActiveRecord
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
+ autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
autoload :SessionStore
@@ -136,6 +136,16 @@ module ActiveRecord
end
end
+ module Tasks
+ extend ActiveSupport::Autoload
+
+ autoload :DatabaseTasks
+ autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
+ autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
+ autoload :PostgreSQLDatabaseTasks,
+ 'active_record/tasks/postgresql_database_tasks'
+ end
+
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
deleted file mode 100644
index 5de10a8dd6..0000000000
--- a/activerecord/lib/active_record/aggregations.rb
+++ /dev/null
@@ -1,262 +0,0 @@
-module ActiveRecord
- # = Active Record Aggregations
- module Aggregations # :nodoc:
- extend ActiveSupport::Concern
-
- def clear_aggregation_cache #:nodoc:
- @aggregation_cache.clear if persisted?
- end
-
- # Active Record implements aggregation through a macro-like class method called +composed_of+
- # for representing attributes as value objects. It expresses relationships like "Account [is]
- # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
- # to the macro adds a description of how the value objects are created from the attributes of
- # the entity object (when the entity is initialized either as a new object or from finding an
- # existing object) and how it can be turned back into attributes (when the entity is saved to
- # the database).
- #
- # class Customer < ActiveRecord::Base
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
- # end
- #
- # The customer class now has the following methods to manipulate the value objects:
- # * <tt>Customer#balance, Customer#balance=(money)</tt>
- # * <tt>Customer#address, Customer#address=(address)</tt>
- #
- # These methods will operate with value objects like the ones described below:
- #
- # class Money
- # include Comparable
- # attr_reader :amount, :currency
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
- #
- # def initialize(amount, currency = "USD")
- # @amount, @currency = amount, currency
- # end
- #
- # def exchange_to(other_currency)
- # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
- # Money.new(exchanged_amount, other_currency)
- # end
- #
- # def ==(other_money)
- # amount == other_money.amount && currency == other_money.currency
- # end
- #
- # def <=>(other_money)
- # if currency == other_money.currency
- # amount <=> other_money.amount
- # else
- # amount <=> other_money.exchange_to(currency).amount
- # end
- # end
- # end
- #
- # class Address
- # attr_reader :street, :city
- # def initialize(street, city)
- # @street, @city = street, city
- # end
- #
- # def close_to?(other_address)
- # city == other_address.city
- # end
- #
- # def ==(other_address)
- # city == other_address.city && street == other_address.street
- # end
- # end
- #
- # Now it's possible to access attributes from the database through the value objects instead. If
- # you choose to name the composition the same as the attribute's name, it will be the only way to
- # access that attribute. That's the case with our +balance+ attribute. You interact with the value
- # objects just like you would with any other attribute:
- #
- # customer.balance = Money.new(20) # sets the Money value object and the attribute
- # customer.balance # => Money value object
- # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
- # customer.balance > Money.new(10) # => true
- # customer.balance == Money.new(20) # => true
- # customer.balance < Money.new(5) # => false
- #
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order
- # of the mappings will determine the order of the parameters.
- #
- # customer.address_street = "Hyancintvej"
- # customer.address_city = "Copenhagen"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- #
- # customer.address_street = "Vesterbrogade"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- # customer.clear_aggregation_cache
- # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
- #
- # customer.address = Address.new("May Street", "Chicago")
- # customer.address_street # => "May Street"
- # customer.address_city # => "Chicago"
- #
- # == Writing value objects
- #
- # Value objects are immutable and interchangeable objects that represent a given value, such as
- # a Money object representing $5. Two Money objects both representing $5 should be equal (through
- # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
- # unlike entity objects where equality is determined by identity. An entity class such as Customer can
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is
- # determined by object or relational unique identifiers (such as primary keys). Normal
- # ActiveRecord::Base classes are entity objects.
- #
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have
- # its amount changed after creation. Create a new Money object with the new value instead. The
- # Money#exchange_to method is an example of this. It returns a new value object instead of changing
- # its own values. Active Record won't persist value objects that have been changed through means
- # other than the writer method.
- #
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
- # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
- #
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
- # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
- #
- # == Custom constructors and converters
- #
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
- # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
- # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
- # a custom constructor to be specified.
- #
- # When a new value is assigned to the value object, the default assumption is that the new value
- # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
- # converted to an instance of value class if necessary.
- #
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
- # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
- # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
- # values can be assigned to the value object using either another NetAddr::CIDR object, a string
- # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
- # these requirements:
- #
- # class NetworkResource < ActiveRecord::Base
- # composed_of :cidr,
- # :class_name => 'NetAddr::CIDR',
- # :mapping => [ %w(network_address network), %w(cidr_range bits) ],
- # :allow_nil => true,
- # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
- # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
- # end
- #
- # # This calls the :constructor
- # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
- #
- # # These assignments will both use the :converter
- # network_resource.cidr = [ '192.168.2.1', 8 ]
- # network_resource.cidr = '192.168.0.1/24'
- #
- # # This assignment won't use the :converter as the value is already an instance of the value class
- # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
- #
- # # Saving and then reloading will use the :constructor on reload
- # network_resource.save
- # network_resource.reload
- #
- # == Finding records by a value object
- #
- # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
- # by specifying an instance of the value object in the conditions hash. The following example
- # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
- #
- # Customer.where(:balance => Money.new(20, "USD")).all
- #
- module ClassMethods
- # Adds reader and writer methods for manipulating a value object:
- # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
- #
- # Options are:
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
- # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
- # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
- # with this option.
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
- # object. Each mapping is represented as an array where the first item is the name of the
- # entity attribute and the second item is the name of the attribute in the value object. The
- # order in which mappings are defined determines the order in which attributes are sent to the
- # value class constructor.
- # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
- # mapped attributes.
- # This defaults to +false+.
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
- # is called to initialize the value object. The constructor is passed all of the mapped attributes,
- # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
- # to instantiate a <tt>:class_name</tt> object.
- # The default is <tt>:new</tt>.
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
- # or a Proc that is called when a new value is assigned to the value object. The converter is
- # passed the single value that is used in the assignment and is only called if the new value is
- # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
- # can return nil to skip the assignment.
- #
- # Option examples:
- # composed_of :temperature, :mapping => %w(reading celsius)
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
- # :converter => Proc.new { |balance| balance.to_money }
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
- # composed_of :gps_location
- # composed_of :gps_location, :allow_nil => true
- # composed_of :ip_address,
- # :class_name => 'IPAddr',
- # :mapping => %w(ip to_i),
- # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
- # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
- #
- def composed_of(part_id, options = {})
- options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
-
- name = part_id.id2name
- class_name = options[:class_name] || name.camelize
- mapping = options[:mapping] || [ name, name ]
- mapping = [ mapping ] unless mapping.first.is_a?(Array)
- allow_nil = options[:allow_nil] || false
- constructor = options[:constructor] || :new
- converter = options[:converter]
-
- reader_method(name, class_name, mapping, allow_nil, constructor)
- writer_method(name, class_name, mapping, allow_nil, converter)
-
- create_reflection(:composed_of, part_id, options, self)
- end
-
- private
- def reader_method(name, class_name, mapping, allow_nil, constructor)
- define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
- object = constructor.respond_to?(:call) ?
- constructor.call(*attrs) :
- class_name.constantize.send(constructor, *attrs)
- @aggregation_cache[name] = object
- end
- @aggregation_cache[name]
- end
- end
-
- def writer_method(name, class_name, mapping, allow_nil, converter)
- define_method("#{name}=") do |part|
- unless part.is_a?(class_name.constantize) || converter.nil? || part.nil?
- part = converter.respond_to?(:call) ?
- converter.call(part) :
- class_name.constantize.send(converter, part)
- end
-
- if part.nil? && allow_nil
- mapping.each { |pair| self[pair.first] = nil }
- @aggregation_cache[name] = nil
- else
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
- @aggregation_cache[name] = part.freeze
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 5a44d3a156..89a626693d 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -96,7 +96,7 @@ module ActiveRecord
conditions.each do |condition|
if options[:through] && condition.is_a?(Hash)
- condition = { table.name => condition }
+ condition = disambiguate_condition(table, condition)
end
scope = scope.where(interpolate(condition))
@@ -113,7 +113,7 @@ module ActiveRecord
conditions.each do |condition|
condition = interpolate(condition)
- condition = { (table.table_alias || table.name) => condition } unless i == 0
+ condition = disambiguate_condition(table, condition) unless i == 0
scope = scope.where(condition)
end
@@ -138,6 +138,21 @@ module ActiveRecord
end
end
+ def disambiguate_condition(table, condition)
+ if condition.is_a?(Hash)
+ Hash[
+ condition.map do |k, v|
+ if v.is_a?(Hash)
+ [k, v]
+ else
+ [table.table_alias || table.name, { k => v }]
+ end
+ end
+ ]
+ else
+ condition
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 4ec176e641..2447914640 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -50,18 +50,7 @@ module ActiveRecord
end
else
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- relation = scoped
-
- including = (relation.eager_load_values + relation.includes_values).uniq
-
- if including.any?
- join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
- relation = join_dependency.join_associations.inject(relation) do |r, association|
- association.join_relation(r)
- end
- end
-
- relation.pluck(column)
+ scoped.pluck(column)
end
end
@@ -230,6 +219,7 @@ module ActiveRecord
delete_records(:all, dependent)
end
else
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
delete_or_destroy(records, dependent)
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index fa316a8c9d..2fb80fdc4c 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -37,9 +37,107 @@ module ActiveRecord
delegate :target, :load_target, :loaded?, :to => :@association
##
+ # :method: select
+ #
+ # :call-seq:
+ # select(select = nil)
+ # select(&block)
+ #
+ # Works in two ways.
+ #
+ # *First:* Specify a subset of fields to be selected from the result set.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.select(:name)
+ # # => [
+ # # #<Pet id: nil, name: "Fancy-Fancy">,
+ # # #<Pet id: nil, name: "Spook">,
+ # # #<Pet id: nil, name: "Choo-Choo">
+ # # ]
+ #
+ # person.pets.select([:id, :name])
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy">,
+ # # #<Pet id: 2, name: "Spook">,
+ # # #<Pet id: 3, name: "Choo-Choo">
+ # # ]
+ #
+ # Be careful because this also means you’re initializing a model
+ # object with only the fields that you’ve selected. If you attempt
+ # to access a field that is not in the initialized record you’ll
+ # receive:
+ #
+ # person.pets.select(:name).first.person_id
+ # # => ActiveModel::MissingAttributeError: missing attribute: person_id
+ #
+ # *Second:* You can pass a block so it can be used just like Array#select.
+ # This build an array of objects from the database for the scope,
+ # converting them into an array and iterating through them using
+ # Array#select.
+ #
+ # person.pets.select { |pet| pet.name =~ /oo/ }
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
+ # # => [
+ # # #<Pet id: 2, name: "Spook">,
+ # # #<Pet id: 3, name: "Choo-Choo">
+ # # ]
+
+ ##
+ # :method: find
+ #
+ # :call-seq:
+ # find(*args, &block)
+ #
+ # Finds an object in the collection responding to the +id+. Uses the same
+ # rules as +ActiveRecord::Base.find+. Returns +ActiveRecord::RecordNotFound++
+ # error if the object can not be found.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
+ # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
+ #
+ # person.pets.find(2) { |pet| pet.name.downcase! }
+ # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
+ #
+ # person.pets.find(2, 3)
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+
+ ##
# :method: first
+ #
+ # :call-seq:
+ # first(limit = nil)
+ #
# Returns the first record, or the first +n+ records, from the collection.
- # If the collection is empty, the first form returns nil, and the second
+ # If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
#
# class Person < ActiveRecord::Base
@@ -67,8 +165,12 @@ module ActiveRecord
##
# :method: last
+ #
+ # :call-seq:
+ # last(limit = nil)
+ #
# Returns the last record, or the last +n+ records, from the collection.
- # If the collection is empty, the first form returns nil, and the second
+ # If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
#
# class Person < ActiveRecord::Base
@@ -95,7 +197,95 @@ module ActiveRecord
# another_person_without.pets.last(3) # => []
##
+ # :method: build
+ #
+ # :call-seq:
+ # build(attributes = {}, options = {}, &block)
+ #
+ # Returns a new object of the collection type that has been instantiated
+ # with +attributes+ and linked to this object, but have not yet been saved.
+ # You can pass an array of attributes hashes, this will return an array
+ # with the new objects.
+ #
+ # class Person
+ # has_many :pets
+ # end
+ #
+ # person.pets.build
+ # # => #<Pet id: nil, name: nil, person_id: 1>
+ #
+ # person.pets.build(name: 'Fancy-Fancy')
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
+ #
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
+ # # => [
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 5 # size of the collection
+ # person.pets.count # => 0 # count from database
+
+ ##
+ # :method: create
+ #
+ # :call-seq:
+ # create(attributes = {}, options = {}, &block)
+ #
+ # Returns a new object of the collection type that has been instantiated with
+ # attributes, linked to this object and that has already been saved (if it
+ # passes the validations).
+ #
+ # class Person
+ # has_many :pets
+ # end
+ #
+ # person.pets.create(name: 'Fancy-Fancy')
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
+ #
+ # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 3
+ # person.pets.count # => 3
+ #
+ # person.pets.find(1, 2, 3)
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+
+ ##
+ # :method: create!
+ #
+ # :call-seq:
+ # create!(attributes = {}, options = {}, &block)
+ #
+ # Like +create+, except that if the record is invalid, raises an exception.
+ #
+ # class Person
+ # has_many :pets
+ # end
+ #
+ # class Pet
+ # attr_accessible :name
+ # validates :name, presence: true
+ # end
+ #
+ # person.pets.create!(name: nil)
+ # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
+
+ ##
# :method: concat
+ #
+ # :call-seq:
+ # concat(*records)
+ #
# Add one or more records to the collection by setting their foreign keys
# to the association's primary key. Since << flattens its argument list and
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
@@ -123,6 +313,10 @@ module ActiveRecord
##
# :method: replace
+ #
+ # :call-seq:
+ # replace(other_array)
+ #
# Replace this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
#
@@ -147,23 +341,425 @@ module ActiveRecord
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
##
+ # :method: delete_all
+ #
+ # :call-seq:
+ # delete_all()
+ #
+ # Deletes all the records from the collection. For +has_many+ asssociations,
+ # the deletion is done according to the strategy specified by the <tt>:dependent</tt>
+ # option. Returns an array with the deleted records.
+ #
+ # If no <tt>:dependent</tt> option is given, then it will follow the
+ # default strategy. The default strategy is <tt>:nullify</tt>. This
+ # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
+ # the default strategy is +delete_all+.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets # dependent: :nullify option by default
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete_all
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 0
+ # person.pets # => []
+ #
+ # Pet.find(1, 2, 3)
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
+ # # #<Pet id: 2, name: "Spook", person_id: nil>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
+ # # ]
+ #
+ # If it is set to <tt>:destroy</tt> all the objects from the collection
+ # are removed by calling their +destroy+ method. See +destroy+ for more
+ # information.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets, dependent: :destroy
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete_all
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # Pet.find(1, 2, 3)
+ # # => ActiveRecord::RecordNotFound
+ #
+ # If it is set to <tt>:delete_all</tt>, all the objects are deleted
+ # *without* calling their +destroy+ method.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets, dependent: :delete_all
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete_all
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # Pet.find(1, 2, 3)
+ # # => ActiveRecord::RecordNotFound
+
+ ##
# :method: destroy_all
- # Destroy all the records from this association.
+ #
+ # :call-seq:
+ # destroy_all()
+ #
+ # Deletes the records of the collection directly from the database.
+ # This will _always_ remove the records ignoring the +:dependent+
+ # option.
#
# class Person < ActiveRecord::Base
# has_many :pets
# end
#
# person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
#
# person.pets.destroy_all
#
# person.pets.size # => 0
# person.pets # => []
+ #
+ # Pet.find(1) # => Couldn't find Pet with id=1
+
+ ##
+ # :method: delete
+ #
+ # :call-seq:
+ # delete(*records)
+ # delete(*fixnum_ids)
+ # delete(*string_ids)
+ #
+ # Deletes the +records+ supplied and removes them from the collection. For
+ # +has_many+ associations, the deletion is done according to the strategy
+ # specified by the <tt>:dependent</tt> option. Returns an array with the
+ # deleted records.
+ #
+ # If no <tt>:dependent</tt> option is given, then it will follow the default
+ # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
+ # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
+ # strategy is +delete_all+.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets # dependent: :nullify option by default
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete(Pet.find(1))
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.size # => 2
+ # person.pets
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # Pet.find(1)
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
+ #
+ # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
+ # their +destroy+ method. See +destroy+ for more information.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets, dependent: :destroy
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete(Pet.find(1), Pet.find(3))
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 1
+ # person.pets
+ # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
+ #
+ # Pet.find(1, 3)
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
+ #
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
+ # *without* calling their +destroy+ method.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets, dependent: :delete_all
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete(Pet.find(1))
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.size # => 2
+ # person.pets
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # Pet.find(1)
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
+ #
+ # You can pass +Fixnum+ or +String+ values, it finds the records
+ # responding to the +id+ and executes delete on them.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.delete("1")
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.delete(2, 3)
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+
+ ##
+ # :method: destroy
+ #
+ # :call-seq:
+ # destroy(*records)
+ #
+ # Destroys the +records+ supplied and removes them from the collection.
+ # This method will _always_ remove record from the database ignoring
+ # the +:dependent+ option. Returns an array with the removed records.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.destroy(Pet.find(1))
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
+ #
+ # person.pets.size # => 2
+ # person.pets
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.destroy(Pet.find(2), Pet.find(3))
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 0
+ # person.pets # => []
+ #
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
+ #
+ # You can pass +Fixnum+ or +String+ values, it finds the records
+ # responding to the +id+ and then deletes them from the database.
+ #
+ # person.pets.size # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
+ #
+ # person.pets.destroy("4")
+ # # => #<Pet id: 4, name: "Benny", person_id: 1>
+ #
+ # person.pets.size # => 2
+ # person.pets
+ # # => [
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
+ #
+ # person.pets.destroy(5, 6)
+ # # => [
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 0
+ # person.pets # => []
+ #
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
+
+ ##
+ # :method: uniq
+ #
+ # :call-seq:
+ # uniq()
+ #
+ # Specifies whether the records should be unique or not.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.select(:name)
+ # # => [
+ # # #<Pet name: "Fancy-Fancy">,
+ # # #<Pet name: "Fancy-Fancy">
+ # # ]
+ #
+ # person.pets.select(:name).uniq
+ # # => [#<Pet name: "Fancy-Fancy">]
+
+ ##
+ # :method: count
+ #
+ # :call-seq:
+ # count()
+ #
+ # Count all records using SQL.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.count # => 3
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+
+ ##
+ # :method: size
+ #
+ # :call-seq:
+ # size()
+ #
+ # Returns the size of the collection. If the collection hasn't been loaded,
+ # it executes a <tt>SELECT COUNT(*)</tt> query.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.size # => 3
+ # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
+ #
+ # person.pets # This will execute a SELECT * FROM query
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.size # => 3
+ # # Because the collection is already loaded, this will behave like
+ # # collection.size and no SQL count query is executed.
+
+ ##
+ # :method: length
+ #
+ # :call-seq:
+ # length()
+ #
+ # Returns the size of the collection calling +size+ on the target.
+ # If the collection has been already loaded, +length+ and +size+ are
+ # equivalent.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.length # => 3
+ # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
+ #
+ # # Because the collection is loaded, you can
+ # # call the collection with no additional queries:
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
##
# :method: empty?
- # Returns true if the collection is empty.
+ #
+ # Returns +true+ if the collection is empty.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -179,7 +775,12 @@ module ActiveRecord
##
# :method: any?
- # Returns true if the collection is not empty.
+ #
+ # :call-seq:
+ # any?
+ # any?{|item| block}
+ #
+ # Returns +true+ if the collection is not empty.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -211,8 +812,13 @@ module ActiveRecord
##
# :method: many?
+ #
+ # :call-seq:
+ # many?
+ # many?{|item| block}
+ #
# Returns true if the collection has more than one record.
- # Equivalent to +collection.size > 1+.
+ # Equivalent to <tt>collection.size > 1</tt>.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -248,7 +854,11 @@ module ActiveRecord
##
# :method: include?
- # Returns true if the given object is present in the collection.
+ #
+ # :call-seq:
+ # include?(record)
+ #
+ # Returns +true+ if the given object is present in the collection.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -265,7 +875,7 @@ module ActiveRecord
:any?, :many?, :include?,
:to => :@association
- def initialize(association)
+ def initialize(association) #:nodoc:
@association = association
super association.klass, association.klass.arel_table
merge! association.scoped
@@ -297,10 +907,67 @@ module ActiveRecord
end
end
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
+ # contain the same number of elements and if each element is equal
+ # to the corresponding element in the other array, otherwise returns
+ # +false+.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
+ # # ]
+ #
+ # other = person.pets.to_ary
+ #
+ # person.pets == other
+ # # => true
+ #
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
+ #
+ # person.pets == other
+ # # => false
def ==(other)
load_target == other
end
+ # Returns a new array of objects from the collection. If the collection
+ # hasn't been loaded, it fetches the records from the database.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
+ #
+ # other_pets = person.pets.to_ary
+ # # => [
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
+ #
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
+ #
+ # other_pets
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
+ #
+ # person.pets
+ # # This is not affected by replace
+ # # => [
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
+ # # ]
def to_ary
load_target.dup
end
@@ -331,37 +998,32 @@ module ActiveRecord
end
alias_method :push, :<<
- # Removes every object from the collection. This does not destroy
- # the objects, it sets their foreign keys to +NULL+. Returns +self+
- # so methods can be chained.
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
+ # of an array with the deleted objects, so methods can be chained. See
+ # +delete_all+ for more information.
+ def clear
+ delete_all
+ self
+ end
+
+ # Reloads the collection from the database. Returns +self+.
+ # Equivalent to <tt>collection(true)</tt>.
#
# class Person < ActiveRecord::Base
# has_many :pets
# end
#
- # person.pets # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
- # person.pets.clear # => []
- # person.pets.size # => 0
- #
- # Pet.find(1) # => #<Pet id: 1, name: "Snoop", group: "dogs", person_id: nil>
+ # person.pets # fetches pets from the database
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
#
- # If they are associated with +dependent: :destroy+ option, it deletes
- # them directly from the database.
+ # person.pets # uses the pets cache
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
#
- # class Person < ActiveRecord::Base
- # has_many :pets, dependent: :destroy
- # end
+ # person.pets.reload # fetches pets from the database
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
#
- # person.pets # => [#<Pet id: 2, name: "Gorby", group: "cats", person_id: 2>]
- # person.pets.clear # => []
- # person.pets.size # => 0
- #
- # Pet.find(2) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=2
- def clear
- delete_all
- self
- end
-
+ # person.pets(true)  # fetches pets from the database
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
proxy_association.reload
self
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 2131edbc20..f0d1120c68 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -36,7 +36,7 @@ module ActiveRecord
when :destroy
target.destroy
when :nullify
- target.update_attribute(reflection.foreign_key, nil)
+ target.update_column(reflection.foreign_key, nil)
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index fafed94ff2..54705e4950 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -12,7 +12,7 @@ module ActiveRecord
# and all of its books via a single query:
#
# SELECT * FROM authors
- # LEFT OUTER JOIN books ON authors.id = books.id
+ # LEFT OUTER JOIN books ON authors.id = books.author_id
# WHERE authors.name = 'Ken Akamatsu'
#
# However, this could result in many rows that contain redundant data. After
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index bf9fe70b31..ab6d253ef8 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -1,11 +1,25 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :whitelist_attributes, instance_accessor: false
+ mattr_accessor :mass_assignment_sanitizer, instance_accessor: false
+ end
+
module AttributeAssignment
extend ActiveSupport::Concern
include ActiveModel::MassAssignmentSecurity
+ included do
+ initialize_mass_assignment_sanitizer
+ end
+
module ClassMethods
+ def inherited(child) # :nodoc:
+ child.send :initialize_mass_assignment_sanitizer if self == Base
+ super
+ end
+
private
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
@@ -14,6 +28,11 @@ module ActiveRecord
default << 'id' unless primary_key.eql? 'id'
default
end
+
+ def initialize_mass_assignment_sanitizer
+ attr_accessible(nil) if Model.whitelist_attributes
+ self.mass_assignment_sanitizer = Model.mass_assignment_sanitizer if Model.mass_assignment_sanitizer
+ end
end
# Allows you to set all the attributes at once by passing in a hash with keys
@@ -64,11 +83,12 @@ module ActiveRecord
# user.name # => "Josh"
# user.is_admin? # => true
def assign_attributes(new_attributes, options = {})
- return unless new_attributes
+ return if new_attributes.blank?
attributes = new_attributes.stringify_keys
multi_parameter_attributes = []
nested_parameter_attributes = []
+ previous_options = @mass_assignment_options
@mass_assignment_options = options
unless options[:without_protection]
@@ -94,8 +114,9 @@ module ActiveRecord
send("#{k}=", v)
end
- @mass_assignment_options = nil
assign_multiparameter_attributes(multi_parameter_attributes)
+ ensure
+ @mass_assignment_options = previous_options
end
protected
@@ -111,7 +132,7 @@ module ActiveRecord
private
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # by calling new on the column type or aggregation type object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
@@ -146,7 +167,7 @@ module ActiveRecord
end
def read_value_from_parameter(name, values_hash_from_param)
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ klass = column_for_attribute(name).klass
if values_hash_from_param.values.all?{|v|v.nil?}
nil
elsif klass == Time
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 39ea885246..f36df4a444 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -16,19 +16,6 @@ module ActiveRecord
include TimeZoneConversion
include Dirty
include Serialization
-
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
- # (Alias for the protected read_attribute method).
- def [](attr_name)
- read_attribute(attr_name)
- end
-
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
- # (Alias for the protected write_attribute method).
- def []=(attr_name, value)
- write_attribute(attr_name, value)
- end
end
module ClassMethods
@@ -149,7 +136,9 @@ module ActiveRecord
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
def attributes
- Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
+ attribute_names.each_with_object({}) { |name, attrs|
+ attrs[name] = read_attribute(name)
+ }
end
# Returns an <tt>#inspect</tt>-like string for the value of the
@@ -190,6 +179,19 @@ module ActiveRecord
self.class.columns_hash[name.to_s]
end
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ # (Alias for the protected read_attribute method).
+ def [](attr_name)
+ read_attribute(attr_name)
+ end
+
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
+ # (Alias for the protected write_attribute method).
+ def []=(attr_name, value)
+ write_attribute(attr_name, value)
+ end
+
protected
def clone_attributes(reader_method = :read_attribute, attributes = {})
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 11c63591e3..a24b4b7839 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,12 +1,18 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :partial_updates, instance_accessor: false
+ self.partial_updates = true
+ end
+
module AttributeMethods
module Dirty
extend ActiveSupport::Concern
+
include ActiveModel::Dirty
- include AttributeMethods::Write
included do
if self < ::ActiveRecord::Timestamp
@@ -14,7 +20,6 @@ module ActiveRecord
end
config_attribute :partial_updates
- self.partial_updates = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
@@ -72,11 +77,8 @@ module ActiveRecord
def _field_changed?(attr, old, value)
if column = column_for_attribute(attr)
- if column.number? && column.null && (old.nil? || old == 0) && value.blank?
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
- # Hence we don't record it as a change if the value changes from nil to ''.
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
- # be typecast back to 0 (''.to_i => 0)
+ if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
+ changes_from_zero_to_string?(old, value))
value = nil
else
value = column.type_cast(value)
@@ -85,6 +87,19 @@ module ActiveRecord
old != value
end
+
+ def changes_from_nil_to_empty_string?(column, old, value)
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
+ # Hence we don't record it as a change if the value changes from nil to ''.
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
+ # be typecast back to 0 (''.to_i => 0)
+ column.null && (old.nil? || old == 0) && value.blank?
+ end
+
+ def changes_from_zero_to_string?(old, value)
+ # For columns with old 0 and value non-empty string
+ old == 0 && value.present? && value != '0'
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index dcc3d79de9..a7af086e43 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,13 +1,17 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :attribute_types_cached_by_default, instance_accessor: false
+ end
+
module AttributeMethods
module Read
extend ActiveSupport::Concern
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
+ ActiveRecord::Model.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
included do
- config_attribute :attribute_types_cached_by_default, :global => true
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+ config_attribute :attribute_types_cached_by_default
end
module ClassMethods
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 165785c8fb..4af4d28b74 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -6,7 +6,7 @@ module ActiveRecord
included do
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
- config_attribute :serialized_attributes
+ class_attribute :serialized_attributes
self.serialized_attributes = {}
end
@@ -72,12 +72,13 @@ module ActiveRecord
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
end
- def initialize_attributes(attributes) #:nodoc:
- super
+ def initialize_attributes(attributes, options = {}) #:nodoc:
+ serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
+ super(attributes, options)
serialized_attributes.each do |key, coder|
if attributes.key?(key)
- attributes[key] = Attribute.new(coder, attributes[key], :serialized)
+ attributes[key] = Attribute.new(coder, attributes[key], serialized)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index ac31b636db..e300c9721f 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -2,6 +2,14 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/inclusion'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :time_zone_aware_attributes, instance_accessor: false
+ self.time_zone_aware_attributes = false
+
+ mattr_accessor :skip_time_zone_conversion_for_attributes, instance_accessor: false
+ self.skip_time_zone_conversion_for_attributes = []
+ end
+
module AttributeMethods
module TimeZoneConversion
class Type # :nodoc:
@@ -22,11 +30,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :time_zone_aware_attributes, :global => true
- self.time_zone_aware_attributes = false
-
+ config_attribute :time_zone_aware_attributes, global: true
config_attribute :skip_time_zone_conversion_for_attributes
- self.skip_time_zone_conversion_for_attributes = []
end
module ClassMethods
@@ -57,8 +62,9 @@ module ActiveRecord
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
time = time.in_time_zone rescue nil if time
+ changed = read_attribute(:#{attr_name}) != time
write_attribute(:#{attr_name}, original_time)
- #{attr_name}_will_change!
+ #{attr_name}_will_change! if changed
@attributes_cache["#{attr_name}"] = time
end
EOV
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index a050fabf35..cc11567506 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -231,30 +231,6 @@ module ActiveRecord
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
#
module Callbacks
- # We can't define callbacks directly on ActiveRecord::Model because
- # it is a module. So we queue up the definitions and execute them
- # when ActiveRecord::Model is included.
- module Register #:nodoc:
- def self.extended(base)
- base.config_attribute :_callbacks_register
- base._callbacks_register = []
- end
-
- def self.setup(base)
- base._callbacks_register.each do |item|
- base.send(*item)
- end
- end
-
- def define_callbacks(*args)
- self._callbacks_register << [:define_callbacks, *args]
- end
-
- def define_model_callbacks(*args)
- self._callbacks_register << [:define_model_callbacks, *args]
- end
- end
-
extend ActiveSupport::Concern
CALLBACKS = [
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 66a0c83c41..f17e7158de 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -1,12 +1,10 @@
+require 'yaml'
+
module ActiveRecord
# :stopdoc:
module Coders
class YAMLColumn
- RESCUE_ERRORS = [ ArgumentError ]
-
- if defined?(Psych) && defined?(Psych::SyntaxError)
- RESCUE_ERRORS << Psych::SyntaxError
- end
+ RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ]
attr_accessor :object_class
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 450ef69744..347d794fa3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -54,13 +54,146 @@ module ActiveRecord
#
# == Options
#
- # There are two connection-pooling-related options that you can add to
+ # There are several connection-pooling-related options that you can add to
# your database connection configuration:
#
# * +pool+: number indicating size of connection pool (default 5)
- # * +wait_timeout+: number of seconds to block and wait for a connection
+ # * +checkout_timeout+: number of seconds to block and wait for a connection
# before giving up and raising a timeout error (default 5 seconds).
+ # * +reaping_frequency+: frequency in seconds to periodically run the
+ # Reaper, which attempts to find and close dead connections, which can
+ # occur if a programmer forgets to close a connection at the end of a
+ # thread or a thread dies unexpectedly. (Default nil, which means don't
+ # run the Reaper).
+ # * +dead_connection_timeout+: number of seconds from last checkout
+ # after which the Reaper will consider a connection reapable. (default
+ # 5 seconds).
class ConnectionPool
+ # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
+ # with which it shares a Monitor. But could be a generic Queue.
+ #
+ # The Queue in stdlib's 'thread' could replace this class except
+ # stdlib's doesn't support waiting with a timeout.
+ class Queue
+ def initialize(lock = Monitor.new)
+ @lock = lock
+ @cond = @lock.new_cond
+ @num_waiting = 0
+ @queue = []
+ end
+
+ # Test if any threads are currently waiting on the queue.
+ def any_waiting?
+ synchronize do
+ @num_waiting > 0
+ end
+ end
+
+ # Return the number of threads currently waiting on this
+ # queue.
+ def num_waiting
+ synchronize do
+ @num_waiting
+ end
+ end
+
+ # Add +element+ to the queue. Never blocks.
+ def add(element)
+ synchronize do
+ @queue.push element
+ @cond.signal
+ end
+ end
+
+ # If +element+ is in the queue, remove and return it, or nil.
+ def delete(element)
+ synchronize do
+ @queue.delete(element)
+ end
+ end
+
+ # Remove all elements from the queue.
+ def clear
+ synchronize do
+ @queue.clear
+ end
+ end
+
+ # Remove the head of the queue.
+ #
+ # If +timeout+ is not given, remove and return the head the
+ # queue if the number of available elements is strictly
+ # greater than the number of threads currently waiting (that
+ # is, don't jump ahead in line). Otherwise, return nil.
+ #
+ # If +timeout+ is given, block if it there is no element
+ # available, waiting up to +timeout+ seconds for an element to
+ # become available.
+ #
+ # Raises:
+ # - ConnectionTimeoutError if +timeout+ is given and no element
+ # becomes available after +timeout+ seconds,
+ def poll(timeout = nil)
+ synchronize do
+ if timeout
+ no_wait_poll || wait_poll(timeout)
+ else
+ no_wait_poll
+ end
+ end
+ end
+
+ private
+
+ def synchronize(&block)
+ @lock.synchronize(&block)
+ end
+
+ # Test if the queue currently contains any elements.
+ def any?
+ !@queue.empty?
+ end
+
+ # A thread can remove an element from the queue without
+ # waiting if an only if the number of currently available
+ # connections is strictly greater than the number of waiting
+ # threads.
+ def can_remove_no_wait?
+ @queue.size > @num_waiting
+ end
+
+ # Removes and returns the head of the queue if possible, or nil.
+ def remove
+ @queue.shift
+ end
+
+ # Remove and return the head the queue if the number of
+ # available elements is strictly greater than the number of
+ # threads currently waiting. Otherwise, return nil.
+ def no_wait_poll
+ remove if can_remove_no_wait?
+ end
+
+ # Waits on the queue up to +timeout+ seconds, then removes and
+ # returns the head of the queue.
+ def wait_poll(timeout)
+ @num_waiting += 1
+
+ t0 = Time.now
+ elapsed = 0
+ loop do
+ @cond.wait(timeout - elapsed)
+
+ return remove if any?
+
+ elapsed = Time.now - t0
+ raise ConnectionTimeoutError if elapsed >= timeout
+ end
+ ensure
+ @num_waiting -= 1
+ end
+ end
+
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
# A reaper instantiated with a nil frequency will never reap the
# connection pool.
@@ -88,7 +221,7 @@ module ActiveRecord
include MonitorMixin
- attr_accessor :automatic_reconnect, :timeout
+ attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
attr_reader :spec, :connections, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -105,7 +238,8 @@ module ActiveRecord
# The cache of reserved connections mapped to threads
@reserved_connections = {}
- @timeout = spec.config[:wait_timeout] || 5
+ @checkout_timeout = spec.config[:checkout_timeout] || 5
+ @dead_connection_timeout = spec.config[:dead_connection_timeout]
@reaper = Reaper.new self, spec.config[:reaping_frequency]
@reaper.run
@@ -115,21 +249,14 @@ module ActiveRecord
@connections = []
@automatic_reconnect = true
- # connections available to be checked out
- @available = []
-
- # number of threads waiting to check out a connection
- @num_waiting = 0
-
- # signal threads waiting
- @cond = new_cond
+ @available = Queue.new self
end
# Hack for tests to be able to add connections. Do not call outside of tests
- def insert_connection_for_test!(c)
+ def insert_connection_for_test!(c) #:nodoc:
synchronize do
@connections << c
- @available << c
+ @available.add c
end
end
@@ -188,7 +315,7 @@ module ActiveRecord
conn.disconnect!
end
@connections = []
- @available = []
+ @available.clear
end
end
@@ -203,8 +330,9 @@ module ActiveRecord
@connections.delete_if do |conn|
conn.requires_reloading?
end
- @available.delete_if do |conn|
- conn.requires_reloading?
+ @available.clear
+ @connections.each do |conn|
+ @available.add conn
end
end
end
@@ -230,16 +358,7 @@ module ActiveRecord
# - PoolFullError: no connection can be obtained from the pool.
def checkout
synchronize do
- conn = nil
-
- if @num_waiting == 0
- conn = acquire_connection
- end
-
- unless conn
- conn = wait_until(@timeout) { acquire_connection }
- end
-
+ conn = acquire_connection
conn.lease
checkout_and_verify(conn)
end
@@ -258,8 +377,7 @@ module ActiveRecord
release conn
- @available.unshift conn
- @cond.signal
+ @available.add conn
end
end
@@ -274,7 +392,7 @@ module ActiveRecord
# from the reserved hash will be a little easier.
release conn
- @cond.signal # can make a new connection now
+ @available.add checkout_new_connection if @available.any_waiting?
end
end
@@ -283,61 +401,37 @@ module ActiveRecord
# or a thread dies unexpectedly.
def reap
synchronize do
- stale = Time.now - @timeout
+ stale = Time.now - @dead_connection_timeout
connections.dup.each do |conn|
remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
end
- @cond.broadcast # may violate fairness
end
end
private
- # Take an available connection or, if possible, create a new
- # one, or nil.
+ # Acquire a connection by one of 1) immediately removing one
+ # from the queue of available connections, 2) creating a new
+ # connection if the pool is not at capacity, 3) waiting on the
+ # queue for a connection to become available.
#
- # Monitor must be held while calling this method.
- #
- # Returns: a newly acquired connection.
+ # Raises:
+ # - PoolFullError if a connection could not be acquired (FIXME:
+ # why not ConnectionTimeoutError?
def acquire_connection
- if @available.any?
- @available.pop
- elsif connections.size < size
+ if conn = @available.poll
+ conn
+ elsif @connections.size < @size
checkout_new_connection
- end
- end
-
- # Wait on +@cond+ until the block returns non-nil. Note that
- # unlike MonitorMixin::ConditionVariable#wait_until, this method
- # does not test the block before the first wait period.
- #
- # Monitor must be held when calling this method.
- #
- # +timeout+: Integer timeout in seconds
- #
- # Returns: the result of the block
- #
- # Raises:
- # - PoolFullError: timeout elapsed before +&block+ returned a connection
- def wait_until(timeout, &block)
- @num_waiting += 1
- begin
+ else
t0 = Time.now
- loop do
- elapsed = Time.now - t0
- if elapsed >= timeout
- msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
- [timeout, elapsed]
- raise PoolFullError, msg
- end
-
- @cond.wait(timeout - elapsed)
-
- conn = yield
- return conn if conn
+ begin
+ @available.poll(@checkout_timeout)
+ rescue ConnectionTimeoutError
+ msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
+ [@checkout_timeout, Time.now - t0]
+ raise PoolFullError, msg
end
- ensure
- @num_waiting -= 1
end
end
@@ -354,11 +448,11 @@ module ActiveRecord
end
def new_connection
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ ActiveRecord::Model.send(spec.adapter_method, spec.config)
end
def current_connection_id #:nodoc:
- ActiveRecord::Base.connection_id ||= Thread.current.object_id
+ ActiveRecord::Model.connection_id ||= Thread.current.object_id
end
def checkout_new_connection
@@ -469,10 +563,12 @@ module ActiveRecord
end
def retrieve_connection_pool(klass)
- pool = get_pool_for_class klass.name
- return pool if pool
- return nil if ActiveRecord::Model == klass
- retrieve_connection_pool klass.active_record_super
+ if !(klass < Model::Tag)
+ get_pool_for_class('ActiveRecord::Model') # default connection
+ else
+ pool = get_pool_for_class(klass.name)
+ pool || retrieve_connection_pool(klass.superclass)
+ end
end
private
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 7b2961a04a..b0b51f540c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -4,6 +4,7 @@ module ActiveRecord
# Converts an arel AST to SQL
def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
+ binds = binds.dup
visitor.accept(arel.ast) do
quote(*binds.shift.reverse)
end
@@ -369,7 +370,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.rolledback!(rollback)
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
@@ -384,7 +385,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.committed!
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 62b0f51bb2..f5794a4e54 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,5 +1,4 @@
require 'active_support/deprecation/reporting'
-require 'active_record/schema_migration'
require 'active_record/migration/join_table'
module ActiveRecord
@@ -549,7 +548,7 @@ module ActiveRecord
if options.is_a?(Hash) && order = options[:order]
case order
when Hash
- column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)}
+ column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
when String
column_names.each {|name| option_strings[name] += " #{order.upcase}"}
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c6faae77cc..28a9821913 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -286,7 +286,7 @@ module ActiveRecord
:name => name,
:connection_id => object_id,
:binds => binds) { yield }
- rescue Exception => e
+ rescue => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger
exception = translate_exception(e, message)
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 9794c5663e..921278d145 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -264,19 +264,19 @@ module ActiveRecord
def begin_db_transaction
execute "BEGIN"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def commit_db_transaction #:nodoc:
execute "COMMIT"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def rollback_db_transaction #:nodoc:
execute "ROLLBACK"
- rescue Exception
+ rescue
# Transactions aren't supported
end
@@ -313,10 +313,10 @@ module ActiveRecord
sql = "SHOW TABLES"
end
- select_all(sql).map { |table|
+ select_all(sql, 'SCHEMA').map { |table|
table.delete('Table_type')
sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
+ exec_without_stmt(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
}.join
end
@@ -508,7 +508,7 @@ module ActiveRecord
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
variables.first['Value'] unless variables.empty?
end
@@ -630,7 +630,7 @@ module ActiveRecord
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
add_column_options!(rename_column_sql, options)
rename_column_sql
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 350ccce03d..8fc172f6e8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -268,7 +268,7 @@ module ActiveRecord
# increase timeout so mysql server doesn't disconnect us
wait_timeout = @config[:wait_timeout]
- wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
variable_assignments << "@@wait_timeout = #{wait_timeout}"
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index df3d5e4657..6657491c06 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -223,6 +223,7 @@ module ActiveRecord
alias_type 'bit', 'text'
alias_type 'varbit', 'text'
alias_type 'macaddr', 'text'
+ alias_type 'uuid', 'text'
# FIXME: I don't think this is correct. We should probably be returning a parsed date,
# but the tests pass with a string returned.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 15c3d7be36..43e243581c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -18,7 +18,7 @@ module ActiveRecord
# Forward any unused config params to PGconn.connect.
[:statement_limit, :encoding, :min_messages, :schema_search_path,
- :schema_order, :adapter, :pool, :wait_timeout, :template,
+ :schema_order, :adapter, :pool, :checkout_timeout, :template,
:reaping_frequency, :insert_returning].each do |key|
conn_params.delete key
end
@@ -89,7 +89,6 @@ module ActiveRecord
else
string
end
-
end
def cidr_to_string(object)
@@ -256,7 +255,7 @@ module ActiveRecord
:integer
# UUID type
when 'uuid'
- :string
+ :uuid
# Small and big integer types
when /^(?:small|big)int$/
:integer
@@ -319,6 +318,10 @@ module ActiveRecord
def macaddr(name, options = {})
column(name, 'macaddr', options)
end
+
+ def uuid(name, options = {})
+ column(name, 'uuid', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -341,7 +344,8 @@ module ActiveRecord
:hstore => { :name => "hstore" },
:inet => { :name => "inet" },
:cidr => { :name => "cidr" },
- :macaddr => { :name => "macaddr" }
+ :macaddr => { :name => "macaddr" },
+ :uuid => { :name => "uuid" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -457,7 +461,8 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- @connection.status == PGconn::CONNECTION_OK
+ @connection.query 'SELECT 1'
+ true
rescue PGError
false
end
@@ -523,7 +528,7 @@ module ActiveRecord
# Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
- @table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
+ @table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
end
# QUOTING ==================================================
@@ -964,28 +969,28 @@ module ActiveRecord
binds = [[nil, table]]
binds << [nil, schema] if schema
- exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind in ('v','r')
- AND c.relname = $1
- AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'}
+ AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
+ AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
SQL
end
# Returns true if schema exists.
def schema_exists?(name)
- exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_namespace
- WHERE nspname = $1
+ WHERE nspname = '#{name}'
SQL
end
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
- result = query(<<-SQL, name)
+ result = query(<<-SQL, 'SCHEMA')
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
@@ -997,7 +1002,6 @@ module ActiveRecord
ORDER BY i.relname
SQL
-
result.map do |row|
index_name = row[0]
unique = row[1] == 't'
@@ -1036,7 +1040,7 @@ module ActiveRecord
# Returns the current database name.
def current_database
- query('select current_database()')[0][0]
+ query('select current_database()', 'SCHEMA')[0][0]
end
# Returns the current schema name.
@@ -1046,7 +1050,7 @@ module ActiveRecord
# Returns the current database encoding format.
def encoding
- query(<<-end_sql)[0][0]
+ query(<<-end_sql, 'SCHEMA')[0][0]
SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
WHERE pg_database.datname LIKE '#{current_database}'
end_sql
@@ -1054,7 +1058,7 @@ module ActiveRecord
# Returns an array of schema names.
def schema_names
- query(<<-SQL).flatten
+ query(<<-SQL, 'SCHEMA').flatten
SELECT nspname
FROM pg_namespace
WHERE nspname !~ '^pg_.*'
@@ -1110,8 +1114,8 @@ module ActiveRecord
end
def serial_sequence(table, column)
- result = exec_query(<<-eosql, 'SCHEMA', [[nil, table], [nil, column]])
- SELECT pg_get_serial_sequence($1, $2)
+ result = exec_query(<<-eosql, 'SCHEMA')
+ SELECT pg_get_serial_sequence('#{table}', '#{column}')
eosql
result.rows.first.first
end
@@ -1188,13 +1192,13 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
- row = exec_query(<<-end_sql, 'SCHEMA', [[nil, table]]).rows.first
+ row = exec_query(<<-end_sql, 'SCHEMA').rows.first
SELECT DISTINCT(attr.attname)
FROM pg_attribute attr
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
WHERE cons.contype = 'p'
- AND dep.refobjid = $1::regclass
+ AND dep.refobjid = '#{table}'::regclass
end_sql
row && row.first
@@ -1274,7 +1278,7 @@ module ActiveRecord
end
when 'integer'
return 'integer' unless limit
-
+
case limit
when 1, 2; 'smallint'
when 3, 4; 'integer'
@@ -1446,7 +1450,7 @@ module ActiveRecord
if @config[:encoding]
@connection.set_client_encoding(@config[:encoding])
end
- self.client_min_messages = @config[:min_messages] if @config[:min_messages]
+ self.client_min_messages = @config[:min_messages] || 'warning'
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
@@ -1471,7 +1475,7 @@ module ActiveRecord
end
def last_insert_id_result(sequence_name) #:nodoc:
- exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
+ exec_query("SELECT currval('#{sequence_name}')", 'SQL')
end
# Executes a SELECT query and returns the results, performing any data type
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d4ffa82b17..a0c7e559ce 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -208,7 +208,6 @@ module ActiveRecord
true
end
-
# QUOTING ==================================================
def quote(value, column = nil)
@@ -220,7 +219,6 @@ module ActiveRecord
end
end
-
def quote_string(s) #:nodoc:
@connection.class.quote(s)
end
@@ -359,7 +357,7 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
+ def tables(name = nil, table_name = nil) #:nodoc:
sql = <<-SQL
SELECT name
FROM sqlite_master
@@ -367,13 +365,13 @@ module ActiveRecord
SQL
sql << " AND name = #{quote_table_name(table_name)}" if table_name
- exec_query(sql, name).map do |row|
+ exec_query(sql, 'SCHEMA').map do |row|
row['name']
end
end
- def table_exists?(name)
- name && tables('SCHEMA', name).any?
+ def table_exists?(table_name)
+ table_name && tables(nil, table_name).any?
end
# Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
@@ -394,12 +392,12 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
IndexDefinition.new(
table_name,
row['name'],
row['unique'] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
+ exec_query("PRAGMA index_info('#{row['name']}')", "Columns for index #{row['name']} on #{table_name}").map { |col|
col['name']
})
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 7b218a5570..bda41df80f 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -90,6 +90,10 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
+ def clear_cache! # :nodoc:
+ connection.schema_cache.clear!
+ end
+
delegate :clear_active_connections!, :clear_reloadable_connections!,
:clear_all_connections!, :to => :connection_handler
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index f2833fbf3c..90f156456e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,79 +1,88 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/module/delegation'
require 'thread'
module ActiveRecord
- module Core
- extend ActiveSupport::Concern
+ ActiveSupport.on_load(:active_record_config) do
+ ##
+ # :singleton-method:
+ #
+ # Accepts a logger conforming to the interface of Log4r which is then
+ # passed on to any new database connections made and which can be
+ # retrieved on both a class and instance level by calling +logger+.
+ mattr_accessor :logger, instance_accessor: false
- included do
- ##
- # :singleton-method:
- # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
- # which is then passed on to any new database connections made and which can be retrieved on both
- # a class and instance level by calling +logger+.
- config_attribute :logger, :global => true
+ ##
+ # :singleton-method:
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
+ mattr_accessor :configurations, instance_accessor: false
+ self.configurations = {}
- ##
- # :singleton-method:
- # Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
- #
- # For example, the following database.yml...
- #
- # development:
- # adapter: sqlite3
- # database: db/development.sqlite3
- #
- # production:
- # adapter: sqlite3
- # database: db/production.sqlite3
- #
- # ...would result in ActiveRecord::Base.configurations to look like this:
- #
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
- config_attribute :configurations, :global => true
- self.configurations = {}
+ ##
+ # :singleton-method:
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
+ mattr_accessor :default_timezone, instance_accessor: false
+ self.default_timezone = :utc
- ##
- # :singleton-method:
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
- # dates and times from the database. This is set to :utc by default.
- config_attribute :default_timezone, :global => true
- self.default_timezone = :utc
+ ##
+ # :singleton-method:
+ # Specifies the format to use when dumping the database schema with Rails'
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ mattr_accessor :schema_format, instance_accessor: false
+ self.schema_format = :ruby
- ##
- # :singleton-method:
- # Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
- # ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
- # adapters for, e.g., your development and test environments.
- config_attribute :schema_format, :global => true
- self.schema_format = :ruby
+ ##
+ # :singleton-method:
+ # Specify whether or not to use timestamps for migration versions
+ mattr_accessor :timestamped_migrations, instance_accessor: false
+ self.timestamped_migrations = true
- ##
- # :singleton-method:
- # Specify whether or not to use timestamps for migration versions
- config_attribute :timestamped_migrations, :global => true
- self.timestamped_migrations = true
+ mattr_accessor :connection_handler, instance_accessor: false
+ self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ mattr_accessor :dependent_restrict_raises, instance_accessor: false
+ self.dependent_restrict_raises = true
+ end
+ module Core
+ extend ActiveSupport::Concern
+
+ included do
##
# :singleton-method:
# The connection handler
config_attribute :connection_handler
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
##
# :singleton-method:
@@ -82,8 +91,11 @@ module ActiveRecord
# ActiveRecord::DeleteRestrictionError exception will be raised
# along with a DEPRECATION WARNING. If set to false, an error would
# be added to the model instead.
- config_attribute :dependent_restrict_raises, :global => true
- self.dependent_restrict_raises = true
+ config_attribute :dependent_restrict_raises
+
+ %w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
+ config_attribute name, global: true
+ end
end
module ClassMethods
@@ -240,7 +252,7 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
- self.class.initialize_attributes(cloned_attributes)
+ self.class.initialize_attributes(cloned_attributes, :serialized => false)
cloned_attributes.delete(self.class.primary_key)
@@ -254,7 +266,6 @@ module ActiveRecord
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
end
- @aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
@@ -379,15 +390,15 @@ module ActiveRecord
@attributes[pk] = nil unless @attributes.key?(pk)
- @aggregation_cache = {}
- @association_cache = {}
- @attributes_cache = {}
- @previously_changed = {}
- @changed_attributes = {}
- @readonly = false
- @destroyed = false
- @marked_for_destruction = false
- @new_record = true
+ @association_cache = {}
+ @attributes_cache = {}
+ @previously_changed = {}
+ @changed_attributes = {}
+ @readonly = false
+ @destroyed = false
+ @marked_for_destruction = false
+ @new_record = true
+ @mass_assignment_options = nil
end
end
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index b163ef3c12..b27a19f89a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -1,111 +1,115 @@
module ActiveRecord
# = Active Record Counter Cache
module CounterCache
- # Resets one or more counter caches to their correct value using an SQL
- # count query. This is useful when adding new counter caches, or if the
- # counter has been corrupted or modified directly by SQL.
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more counter names to reset
- #
- # ==== Examples
- #
- # # For Post with id #1 records reset the comments_count
- # Post.reset_counters(1, :comments)
- def reset_counters(id, *counters)
- object = find(id)
- counters.each do |association|
- has_many_association = reflect_on_association(association.to_sym)
+ extend ActiveSupport::Concern
- foreign_key = has_many_association.foreign_key.to_s
- child_class = has_many_association.klass
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
- counter_name = reflection.counter_cache_column
+ module ClassMethods
+ # Resets one or more counter caches to their correct value using an SQL
+ # count query. This is useful when adding new counter caches, or if the
+ # counter has been corrupted or modified directly by SQL.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to reset a counter on.
+ # * +counters+ - One or more counter names to reset
+ #
+ # ==== Examples
+ #
+ # # For Post with id #1 records reset the comments_count
+ # Post.reset_counters(1, :comments)
+ def reset_counters(id, *counters)
+ object = find(id)
+ counters.each do |association|
+ has_many_association = reflect_on_association(association.to_sym)
- stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
- arel_table[counter_name] => object.send(association).count
- })
- connection.update stmt
- end
- return true
- end
+ foreign_key = has_many_association.foreign_key.to_s
+ child_class = has_many_association.klass
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
+ counter_name = reflection.counter_cache_column
- # A generic "counter updater" implementation, intended primarily to be
- # used by increment_counter and decrement_counter, but which may also
- # be useful on its own. It simply does a direct SQL update for the record
- # with the given ID, altering the given hash of counters by the amount
- # given by the corresponding value:
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
- # * +counters+ - An Array of Hashes containing the names of the fields
- # to update as keys and the amount to update the field by as values.
- #
- # ==== Examples
- #
- # # For the Post with id of 5, decrement the comment_count by 1, and
- # # increment the action_count by 1
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) - 1,
- # # action_count = COALESCE(action_count, 0) + 1
- # # WHERE id = 5
- #
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
- # Post.update_counters [10, 15], :comment_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) + 1
- # # WHERE id IN (10, 15)
- def update_counters(id, counters)
- updates = counters.map do |counter_name, value|
- operator = value < 0 ? '-' : '+'
- quoted_column = connection.quote_column_name(counter_name)
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
+ arel_table[counter_name] => object.send(association).count
+ })
+ connection.update stmt
+ end
+ return true
end
- where(primary_key => id).update_all updates.join(', ')
- end
+ # A generic "counter updater" implementation, intended primarily to be
+ # used by increment_counter and decrement_counter, but which may also
+ # be useful on its own. It simply does a direct SQL update for the record
+ # with the given ID, altering the given hash of counters by the amount
+ # given by the corresponding value:
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
+ # * +counters+ - An Array of Hashes containing the names of the fields
+ # to update as keys and the amount to update the field by as values.
+ #
+ # ==== Examples
+ #
+ # # For the Post with id of 5, decrement the comment_count by 1, and
+ # # increment the action_count by 1
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
+ # # action_count = COALESCE(action_count, 0) + 1
+ # # WHERE id = 5
+ #
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
+ # Post.update_counters [10, 15], :comment_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
+ # # WHERE id IN (10, 15)
+ def update_counters(id, counters)
+ updates = counters.map do |counter_name, value|
+ operator = value < 0 ? '-' : '+'
+ quoted_column = connection.quote_column_name(counter_name)
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ end
- # Increment a number field by one, usually representing a count.
- #
- # This is used for caching aggregate values, so that they don't need to be computed every time.
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
- # shown it would have to run an SQL query to find how many posts and comments there are.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
- #
- # ==== Examples
- #
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
- def increment_counter(counter_name, id)
- update_counters(id, counter_name => 1)
- end
+ where(primary_key => id).update_all updates.join(', ')
+ end
+
+ # Increment a number field by one, usually representing a count.
+ #
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
+ # shown it would have to run an SQL query to find how many posts and comments there are.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be incremented.
+ # * +id+ - The id of the object that should be incremented.
+ #
+ # ==== Examples
+ #
+ # # Increment the post_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:post_count, 5)
+ def increment_counter(counter_name, id)
+ update_counters(id, counter_name => 1)
+ end
- # Decrement a number field by one, usually representing a count.
- #
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be decremented.
- # * +id+ - The id of the object that should be decremented.
- #
- # ==== Examples
- #
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
- def decrement_counter(counter_name, id)
- update_counters(id, counter_name => -1)
+ # Decrement a number field by one, usually representing a count.
+ #
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be decremented.
+ # * +id+ - The id of the object that should be decremented.
+ #
+ # ==== Examples
+ #
+ # # Decrement the post_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:post_count, 5)
+ def decrement_counter(counter_name, id)
+ update_counters(id, counter_name => -1)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e278e62ce7..23aaa319d8 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -56,7 +56,7 @@ module ActiveRecord
end
def valid?
- attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
+ attribute_names.all? { |name| model.columns_hash[name] }
end
def define
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index fc80f3081e..9b88bb8178 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -53,6 +53,10 @@ module ActiveRecord
class RecordNotSaved < ActiveRecordError
end
+ # Raised by ActiveRecord::Base.destroy! when a call to destroy would return false.
+ class RecordNotDestroyed < ActiveRecordError
+ end
+
# Raised when SQL statement cannot be executed by the database (for example, it's often the case for
# MySQL when Ruby driver used is too old).
class StatementInvalid < ActiveRecordError
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 313fdb3487..7ade385c70 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,12 +1,13 @@
+require 'active_support/lazy_load_hooks'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
+ end
+
module Explain
- def self.extended(base)
- # If a query takes longer than these many seconds we log its query plan
- # automatically. nil disables this feature.
- base.config_attribute :auto_explain_threshold_in_seconds, :global => true
- end
+ delegate :auto_explain_threshold_in_seconds, :auto_explain_threshold_in_seconds=, to: 'ActiveRecord::Model'
# If auto explain is enabled, this method triggers EXPLAIN logging for the
# queries triggered by the block if it takes more than the threshold as a
@@ -52,7 +53,7 @@ module ActiveRecord
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
- queries && queries.map do |sql, bind|
+ str = queries && queries.map do |sql, bind|
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
unless bind.empty?
@@ -62,6 +63,12 @@ module ActiveRecord
msg << connection.explain(sql, bind)
end.join("\n")
end.join("\n")
+
+ # Overriding inspect to be more human readable, specially in the console.
+ def str.inspect
+ self
+ end
+ str
end
# Silences automatic EXPLAIN logging for the duration of the block.
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 1f8c4fc203..d5ba343b4c 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -2,9 +2,12 @@ require 'active_support/notifications'
module ActiveRecord
class ExplainSubscriber # :nodoc:
- def call(*args)
+ def start(name, id, payload)
+ # unused
+ end
+
+ def finish(name, id, payload)
if queries = Thread.current[:available_queries_for_explain]
- payload = args.last
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
end
end
diff --git a/activerecord/lib/active_record/fixtures/file.rb b/activerecord/lib/active_record/fixtures/file.rb
index 6547791144..a9cabf5a7b 100644
--- a/activerecord/lib/active_record/fixtures/file.rb
+++ b/activerecord/lib/active_record/fixtures/file.rb
@@ -24,37 +24,33 @@ module ActiveRecord
rows.each(&block)
end
- RESCUE_ERRORS = [ ArgumentError ] # :nodoc:
+ RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ] # :nodoc:
private
- if defined?(Psych) && defined?(Psych::SyntaxError)
- RESCUE_ERRORS << Psych::SyntaxError
- end
-
- def rows
- return @rows if @rows
+ def rows
+ return @rows if @rows
+
+ begin
+ data = YAML.load(render(IO.read(@file)))
+ rescue *RESCUE_ERRORS => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
+ end
+ @rows = data ? validate(data).to_a : []
+ end
- begin
- data = YAML.load(render(IO.read(@file)))
- rescue *RESCUE_ERRORS => error
- raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
+ def render(content)
+ ERB.new(content).result
end
- @rows = data ? validate(data).to_a : []
- end
- def render(content)
- ERB.new(content).result
- end
+ # Validate our unmarshalled data.
+ def validate(data)
+ unless Hash === data || YAML::Omap === data
+ raise Fixture::FormatError, 'fixture is not a hash'
+ end
- # Validate our unmarshalled data.
- def validate(data)
- unless Hash === data || YAML::Omap === data
- raise Fixture::FormatError, 'fixture is not a hash'
+ raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
+ data
end
-
- raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
- data
- end
end
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 46d253b0a7..770083ac13 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -1,13 +1,17 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ # Determine whether to store the full constant name including namespace when using STI
+ mattr_accessor :store_full_sti_class, instance_accessor: false
+ self.store_full_sti_class = true
+ end
+
module Inheritance
extend ActiveSupport::Concern
included do
- # Determine whether to store the full constant name including namespace when using STI
config_attribute :store_full_sti_class
- self.store_full_sti_class = true
end
module ClassMethods
@@ -95,7 +99,7 @@ module ActiveRecord
# Returns the class descending directly from ActiveRecord::Base or an
# abstract class, if any, in the inheritance hierarchy.
def class_of_active_record_descendant(klass)
- unless klass < Model
+ unless klass < Model::Tag
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index a3412582fa..4ce42feb74 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -1,4 +1,9 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :lock_optimistically, instance_accessor: false
+ self.lock_optimistically = true
+ end
+
module Locking
# == What is Optimistic Locking
#
@@ -51,8 +56,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :lock_optimistically, :global => true
- self.lock_optimistically = true
+ config_attribute :lock_optimistically
end
def locking_enabled? #:nodoc:
@@ -168,7 +172,7 @@ module ActiveRecord
# start the lock version at zero. Note we can't use
# <tt>locking_enabled?</tt> at this point as
# <tt>@attributes</tt> may not have been initialized yet.
- def initialize_attributes(attributes) #:nodoc:
+ def initialize_attributes(attributes, options = {}) #:nodoc:
if attributes.key?(locking_column) && lock_optimistically
attributes[locking_column] ||= 0
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 2a9139749d..d58176bc62 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,7 +1,6 @@
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/class/attribute_accessors"
require 'active_support/deprecation'
-require 'active_record/schema_migration'
require 'set'
module ActiveRecord
@@ -33,6 +32,12 @@ module ActiveRecord
end
end
+ class PendingMigrationError < ActiveRecordError#:nodoc:
+ def initialize
+ super("Migrations are pending run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve the issue")
+ end
+ end
+
# = Active Record Migrations
#
# Migrations can manage the evolution of a schema used by several physical
@@ -233,7 +238,7 @@ module ActiveRecord
# add_column :people, :salary, :integer
# Person.reset_column_information
# Person.all.each do |p|
- # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # p.update_column :salary, SalaryCalculator.compute(p)
# end
# end
# end
@@ -253,7 +258,7 @@ module ActiveRecord
# ...
# say_with_time "Updating salaries..." do
# Person.all.each do |p|
- # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # p.update_column :salary, SalaryCalculator.compute(p)
# end
# end
# ...
@@ -327,10 +332,28 @@ module ActiveRecord
class Migration
autoload :CommandRecorder, 'active_record/migration/command_recorder'
+
+ # This class is used to verify that all migrations have been run before
+ # loading a web page if config.active_record.migration_error is set to :page_load
+ class CheckPending
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ ActiveRecord::Migration.check_pending!
+ @app.call(env)
+ end
+ end
+
class << self
attr_accessor :delegate # :nodoc:
end
+ def self.check_pending!
+ raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?
+ end
+
def self.method_missing(name, *args, &block) # :nodoc:
(delegate || superclass.delegate).send(name, *args, &block)
end
@@ -606,6 +629,14 @@ module ActiveRecord
end
end
+ def needs_migration?
+ current_version < last_version
+ end
+
+ def last_version
+ migrations(migrations_paths).last.try(:version)||0
+ end
+
def proper_table_name(name)
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 105d1e0e2b..7b3d926d91 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -1,6 +1,34 @@
require 'active_support/deprecation'
+require 'active_support/concern'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ module Configuration # :nodoc:
+ # This just abstracts out how we define configuration options in AR. Essentially we
+ # have mattr_accessors on the ActiveRecord:Model constant that define global defaults.
+ # Classes that then use AR get class_attributes defined, which means that when they
+ # are assigned the default will be overridden for that class and subclasses. (Except
+ # when options[:global] == true, in which case there is one global value always.)
+ def config_attribute(name, options = {})
+ if options[:global]
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def self.#{name}; ActiveRecord::Model.#{name}; end
+ def #{name}; ActiveRecord::Model.#{name}; end
+ def self.#{name}=(val); ActiveRecord::Model.#{name} = val; end
+ CODE
+ else
+ options[:instance_writer] ||= false
+ class_attribute name, options
+
+ singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ remove_method :#{name}
+ def #{name}; ActiveRecord::Model.#{name}; end
+ CODE
+ end
+ end
+ end
+
# <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence.
# This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example:
#
@@ -9,41 +37,35 @@ module ActiveRecord
# end
#
module Model
- module ClassMethods #:nodoc:
- include ActiveSupport::Callbacks::ClassMethods
- include ActiveModel::Naming
- include QueryCache::ClassMethods
- include ActiveSupport::Benchmarkable
- include ActiveSupport::DescendantsTracker
-
- include Querying
- include Translation
- include DynamicMatchers
- include CounterCache
- include Explain
- include ConnectionHandling
- end
+ extend ActiveSupport::Concern
+ extend ConnectionHandling
+ extend ActiveModel::Observing::ClassMethods
- def self.included(base)
- return if base.singleton_class < ClassMethods
+ # This allows us to detect an ActiveRecord::Model while it's in the process of being included.
+ module Tag; end
+ def self.append_features(base)
base.class_eval do
- extend ClassMethods
- Callbacks::Register.setup(self)
- initialize_generated_modules unless self == Base
+ include Tag
+ extend Configuration
end
+
+ super
end
- extend ActiveModel::Configuration
- extend ActiveModel::Callbacks
- extend ActiveModel::MassAssignmentSecurity::ClassMethods
- extend ActiveModel::AttributeMethods::ClassMethods
- extend Callbacks::Register
- extend Explain
- extend ConnectionHandling
+ included do
+ extend ActiveModel::Naming
+ extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
+
+ extend QueryCache::ClassMethods
+ extend Querying
+ extend Translation
+ extend DynamicMatchers
+ extend Explain
+ extend ConnectionHandling
- def self.extend(*modules)
- ClassMethods.send(:include, *modules)
+ initialize_generated_modules unless self == Base
end
include Persistence
@@ -56,13 +78,21 @@ module ActiveRecord
include AttributeAssignment
include ActiveModel::Conversion
include Validations
- include Locking::Optimistic, Locking::Pessimistic
+ include CounterCache
+ include Locking::Optimistic
+ include Locking::Pessimistic
include AttributeMethods
- include Callbacks, ActiveModel::Observing, Timestamp
+ include Callbacks
+ include ActiveModel::Observing
+ include Timestamp
include Associations
include ActiveModel::SecurePassword
- include AutosaveAssociation, NestedAttributes
- include Aggregations, Transactions, Reflection, Serialization, Store
+ include AutosaveAssociation
+ include NestedAttributes
+ include Transactions
+ include Reflection
+ include Serialization
+ include Store
include Core
class << self
@@ -103,6 +133,15 @@ module ActiveRecord
end
end
+ # This hook is where config accessors on Model get defined.
+ #
+ # We don't want to just open the Model module and add stuff to it in other files, because
+ # that would cause Model to load, which causes all sorts of loading order issues.
+ #
+ # We need this hook rather than just using the :active_record one, because users of the
+ # :active_record hook may need to use config options.
+ ActiveSupport.run_load_hooks(:active_record_config, Model)
+
# Load Base at this point, because the active_record load hook is run in that file.
Base
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 7f38dda11e..e6b76ddc4c 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -1,7 +1,19 @@
require 'active_support/concern'
-require 'active_support/core_ext/class/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :primary_key_prefix_type, instance_accessor: false
+
+ mattr_accessor :table_name_prefix, instance_accessor: false
+ self.table_name_prefix = ""
+
+ mattr_accessor :table_name_suffix, instance_accessor: false
+ self.table_name_suffix = ""
+
+ mattr_accessor :pluralize_table_names, instance_accessor: false
+ self.pluralize_table_names = true
+ end
+
module ModelSchema
extend ActiveSupport::Concern
@@ -13,7 +25,7 @@ module ActiveRecord
# the Product class will look for "productid" instead of "id" as the primary column. If the
# latter is specified, the Product class will look for "product_id" instead of "id". Remember
# that this is a global setting for all Active Records.
- config_attribute :primary_key_prefix_type, :global => true
+ config_attribute :primary_key_prefix_type, global: true
##
# :singleton-method:
@@ -26,14 +38,12 @@ module ActiveRecord
# a namespace by defining a singleton method in the parent module called table_name_prefix which
# returns your chosen prefix.
config_attribute :table_name_prefix
- self.table_name_prefix = ""
##
# :singleton-method:
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
# "people_basecamp"). By default, the suffix is the empty string.
config_attribute :table_name_suffix
- self.table_name_suffix = ""
##
# :singleton-method:
@@ -41,7 +51,6 @@ module ActiveRecord
# 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.
config_attribute :pluralize_table_names
- self.pluralize_table_names = true
end
module ClassMethods
@@ -308,10 +317,6 @@ module ActiveRecord
@relation = nil
end
- def clear_cache! # :nodoc:
- connection.schema_cache.clear!
- end
-
private
# Guesses the table name, but does not decorate it with prefix and suffix information.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 95a2ddcc11..841681e542 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -5,6 +5,11 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :nested_attributes_options, instance_accessor: false
+ self.nested_attributes_options = {}
+ end
+
module NestedAttributes #:nodoc:
class TooManyRecords < ActiveRecordError
end
@@ -13,7 +18,6 @@ module ActiveRecord
included do
config_attribute :nested_attributes_options
- self.nested_attributes_options = {}
end
# = Active Record Nested Attributes
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index c2d3eeb8ce..aca8291d75 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -2,24 +2,24 @@
module ActiveRecord
# = Active Record Null Relation
- class NullRelation < Relation
+ module NullRelation
def exec_queries
@records = []
end
- def pluck(column_name)
+ def pluck(_column_name)
[]
end
- def delete_all(conditions = nil)
+ def delete_all(_conditions = nil)
0
end
- def update_all(updates, conditions = nil, options = {})
+ def update_all(_updates, _conditions = nil, _options = {})
0
end
- def delete(id_or_array)
+ def delete(_id_or_array)
0
end
@@ -51,13 +51,12 @@ module ActiveRecord
0
end
- def calculate(operation, column_name, options = {})
+ def calculate(_operation, _column_name, _options = {})
nil
end
- def exists?(id = false)
+ def exists?(_id = false)
false
end
-
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a1bc39a32d..a23597be28 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -122,6 +122,11 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
+ #
+ # There's a series of callbacks associated with <tt>destroy</tt>. If
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
+ # and <tt>destroy</tt> returns +false+. See
+ # ActiveRecord::Callbacks for further details.
def destroy
raise ReadOnlyRecord if readonly?
destroy_associations
@@ -130,6 +135,17 @@ module ActiveRecord
freeze
end
+ # Deletes the record in the database and freezes this instance to reflect
+ # that no changes should be made (since they can't be persisted).
+ #
+ # There's a series of callbacks associated with <tt>destroy!</tt>. If
+ # the <tt>before_destroy</tt> callback return +false+ the action is cancelled
+ # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
+ # ActiveRecord::Callbacks for further details.
+ def destroy!
+ destroy || raise(ActiveRecord::RecordNotDestroyed)
+ end
+
# Returns an instance of the specified +klass+ with the attributes of the
# current record. This is mostly useful in relation to single-table
# inheritance structures where you want a subclass to appear as the
@@ -151,21 +167,6 @@ module ActiveRecord
became
end
- # Updates a single attribute and saves the record.
- # This is especially useful for boolean flags on existing records. Also note that
- #
- # * Validation is skipped.
- # * Callbacks are invoked.
- # * updated_at/updated_on column is updated if that column is available.
- # * Updates all the attributes that are dirty in this object.
- #
- def update_attribute(name, value)
- name = name.to_s
- verify_readonly_attribute(name)
- send("#{name}=", value)
- save(:validate => false)
- end
-
# Updates a single attribute of an object, without calling save.
#
# * Validation is skipped.
@@ -224,7 +225,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def increment!(attribute, by = 1)
- increment(attribute, by).update_attribute(attribute, self[attribute])
+ increment(attribute, by).update_column(attribute, self[attribute])
end
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
@@ -241,7 +242,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def decrement!(attribute, by = 1)
- decrement(attribute, by).update_attribute(attribute, self[attribute])
+ decrement(attribute, by).update_column(attribute, self[attribute])
end
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
@@ -258,7 +259,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def toggle!(attribute)
- toggle(attribute).update_attribute(attribute, self[attribute])
+ toggle(attribute).update_column(attribute, self[attribute])
end
# Reloads the attributes of this object from the database.
@@ -266,7 +267,6 @@ module ActiveRecord
# may do e.g. record.reload(:lock => true) to reload the same record with
# an exclusive row lock.
def reload(options = nil)
- clear_aggregation_cache
clear_association_cache
fresh_object =
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index eb2769f1ef..9432a70c41 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -30,6 +30,7 @@ module ActiveRecord
)
rake_tasks do
+ require "active_record/base"
load "active_record/railties/databases.rake"
end
@@ -38,10 +39,15 @@ module ActiveRecord
# first time. Also, make it output to STDERR.
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
+ require "active_record/base"
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
+ runner do |app|
+ require "active_record/base"
+ end
+
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
@@ -53,11 +59,15 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end
+ initializer "active_record.migration_error" do |app|
+ if config.active_record.delete(:migration_error) == :page_load
+ config.app_middleware.insert_after "::ActionDispatch::Callbacks",
+ "ActiveRecord::Migration::CheckPending"
+ end
+ end
+
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
- if app.config.active_record.delete(:whitelist_attributes)
- attr_accessible(nil)
- end
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@@ -84,18 +94,12 @@ module ActiveRecord
end
initializer "active_record.set_reloader_hooks" do |app|
- hook = lambda do
- ActiveRecord::Base.clear_reloadable_connections!
- ActiveRecord::Base.clear_cache!
- end
+ hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
- if app.config.reload_classes_only_on_change
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_prepare(&hook)
- end
- else
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_cleanup(&hook)
+ ActiveSupport.on_load(:active_record) do
+ ActionDispatch::Reloader.send(hook) do
+ ActiveRecord::Model.clear_reloadable_connections!
+ ActiveRecord::Model.clear_cache!
end
end
end
@@ -106,20 +110,21 @@ module ActiveRecord
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
ActionDispatch::Reloader.to_prepare do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
end
end
ActiveSupport.on_load(:active_record) do
if app.config.use_schema_cache_dump
filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
+
if File.file?(filename)
cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
- ActiveRecord::Base.connection.schema_cache = cache
+ ActiveRecord::Model.connection.schema_cache = cache
else
warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}."
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index f26e18b1e0..8199b5c2e0 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -14,138 +14,27 @@ db_namespace = namespace :db do
end
namespace :create do
- # desc 'Create all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key, such as the first entry here:
- #
- # defaults: &defaults
- # adapter: mysql
- # username: root
- # password:
- # host: localhost
- #
- # development:
- # database: blog_development
- # *defaults
- next unless config['database']
- # Only connect to local databases
- local_database?(config) { create_database(config) }
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
task :create => :load_config do
- configs_for_environment.each { |config| create_database(config) }
- ActiveRecord::Base.establish_connection(configs_for_environment.first)
- end
-
- def mysql_creation_options(config)
- @charset = ENV['CHARSET'] || 'utf8'
- @collation = ENV['COLLATION'] || 'utf8_unicode_ci'
- {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
- end
-
- def create_database(config)
- begin
- if config['adapter'] =~ /sqlite/
- if File.exist?(config['database'])
- $stderr.puts "#{config['database']} already exists"
- else
- begin
- # Create the SQLite database
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- return # Skip the else clause of begin/rescue
- else
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- end
- rescue
- case config['adapter']
- when /mysql/
- if config['adapter'] =~ /jdbc/
- #FIXME After Jdbcmysql gives this class
- require 'active_record/railties/jdbcmysql_error'
- error_class = ArJdbcMySQL::Error
- else
- error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
- end
- access_denied_error = 1045
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => nil))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.establish_connection(config)
- rescue error_class => sqlerr
- if sqlerr.errno == access_denied_error
- print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>"
- root_password = $stdin.gets.strip
- grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \
- "TO '#{config['username']}'@'localhost' " \
- "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
- ActiveRecord::Base.establish_connection(config.merge(
- 'database' => nil, 'username' => 'root', 'password' => root_password))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.connection.execute grant_statement
- ActiveRecord::Base.establish_connection(config)
- else
- $stderr.puts sqlerr.error
- $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}"
- $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
- end
- end
- when /postgresql/
- @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
- ActiveRecord::Base.establish_connection(config)
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- else
- $stderr.puts "#{config['database']} already exists"
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_current
end
namespace :drop do
- # desc 'Drops all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key
- next unless config['database']
- begin
- # Only connect to local databases
- local_database?(config) { drop_database(config) }
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)'
task :drop => :load_config do
- configs_for_environment.each { |config| drop_database_and_rescue(config) }
- end
-
- def local_database?(config, &block)
- if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank?
- yield
- else
- $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_current
end
-
desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
@@ -247,31 +136,18 @@ db_namespace = namespace :db do
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
- task :reset => :environment do
+ task :reset => [:environment, :load_config] do
db_namespace["drop"].invoke
db_namespace["setup"].invoke
end
# desc "Retrieves the charset for the current environment's database"
- task :charset => :environment do
- config = ActiveRecord::Base.configurations[Rails.env || 'development']
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.charset
- when /postgresql/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.encoding
- when /sqlite/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.encoding
- else
- $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
- end
+ task :charset => [:environment, :load_config] do
+ puts ActiveRecord::Tasks::DatabaseTasks.charset_current
end
# desc "Retrieves the collation for the current environment's database"
- task :collation => :environment do
+ task :collation => [:environment, :load_config] do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when /mysql/
@@ -283,12 +159,12 @@ db_namespace = namespace :db do
end
desc 'Retrieves the current schema version number'
- task :version => :environment do
+ task :version => [:environment, :load_config] do
puts "Current version: #{ActiveRecord::Migrator.current_version}"
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => :environment do
+ task :abort_if_pending_migrations => [:environment, :load_config] do
pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
@@ -311,20 +187,20 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :load => :environment do
+ task :load => [:environment, :load_config] do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :identify => :environment do
+ task :identify => [:environment, :load_config] do
require 'active_record/fixtures'
label, id = ENV['LABEL'], ENV['ID']
@@ -360,7 +236,7 @@ db_namespace = namespace :db do
end
desc 'Load a schema.rb file into the database'
- task :load => :environment do
+ task :load => [:environment, :load_config] do
file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
if File.exists?(file)
load(file)
@@ -375,7 +251,7 @@ db_namespace = namespace :db do
namespace :cache do
desc 'Create a db/schema_cache.dump file.'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
@@ -385,7 +261,7 @@ db_namespace = namespace :db do
end
desc 'Clear a db/schema_cache.dump file.'
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
FileUtils.rm(filename) if File.exists?(filename)
end
@@ -395,24 +271,15 @@ db_namespace = namespace :db do
namespace :structure do
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[Rails.env]['adapter']
- when /mysql/, 'oci', 'oracle'
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(abcs[Rails.env], filename)
+ when 'oci', 'oracle'
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when /postgresql/
- set_psql_env(abcs[Rails.env])
- search_path = abcs[Rails.env]['schema_search_path']
- unless search_path.blank?
- search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ")
- end
- `pg_dump -i -s -x -O -f #{filename} #{search_path} #{abcs[Rails.env]['database']}`
- raise 'Error dumping database' if $?.exitstatus == 1
- when /sqlite/
- dbfile = abcs[Rails.env]['database']
- `sqlite3 #{dbfile} .schema > #{filename}`
when 'sqlserver'
`smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f #{filename} -A -U`
when "firebird"
@@ -436,18 +303,8 @@ db_namespace = namespace :db do
abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[env]['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(abcs[env])
- ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.read(filename).split("\n\n").each do |table|
- ActiveRecord::Base.connection.execute(table)
- end
- when /postgresql/
- set_psql_env(abcs[env])
- `psql -f "#{filename}" #{abcs[env]['database']}`
- when /sqlite/
- dbfile = abcs[env]['database']
- `sqlite3 #{dbfile} < "#{filename}"`
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[Rails.env], filename)
when 'sqlserver'
`sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}`
when 'oci', 'oracle'
@@ -505,19 +362,11 @@ db_namespace = namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
# desc "Empty the test database"
- task :purge => :environment do
+ task :purge => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
case abcs['test']['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test']))
- when /postgresql/
- ActiveRecord::Base.clear_active_connections!
- drop_database(abcs['test'])
- create_database(abcs['test'])
- when /sqlite/
- dbfile = abcs['test']['database']
- File.delete(dbfile) if File.exist?(dbfile)
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
when 'sqlserver'
test = abcs.deep_dup['test']
test_database = test['database']
@@ -547,7 +396,7 @@ db_namespace = namespace :db do
namespace :sessions do
# desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
- task :create => :environment do
+ task :create => [:environment, :load_config] do
raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
Rails.application.load_generators
require 'rails/generators/rails/session_migration/session_migration_generator'
@@ -555,7 +404,7 @@ db_namespace = namespace :db do
end
# desc "Clear the sessions table"
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}"
end
end
@@ -591,37 +440,6 @@ end
task 'test:prepare' => 'db:test:prepare'
-def drop_database(config)
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection.drop_database config['database']
- when /sqlite/
- require 'pathname'
- path = Pathname.new(config['database'])
- file = path.absolute? ? path.to_s : File.join(Rails.root, path)
-
- FileUtils.rm(file)
- when /postgresql/
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.drop_database config['database']
- end
-end
-
-def drop_database_and_rescue(config)
- begin
- drop_database(config)
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
-end
-
-def configs_for_environment
- environments = [Rails.env]
- environments << 'test' if Rails.env.development?
- ActiveRecord::Base.configurations.values_at(*environments).compact.reject { |config| config['database'].blank? }
-end
-
def session_table_name
ActiveRecord::SessionStore::Session.table_name
end
@@ -634,10 +452,3 @@ end
def firebird_db_string(config)
FireRuby::Database.db_string_for(config.symbolize_keys)
end
-
-def set_psql_env(config)
- ENV['PGHOST'] = config['host'] if config['host']
- ENV['PGPORT'] = config['port'].to_s if config['port']
- ENV['PGPASSWORD'] = config['password'].to_s if config['password']
- ENV['PGUSER'] = config['username'].to_s if config['username']
-end
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 836b15e2ce..960b78dc38 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -6,7 +6,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :_attr_readonly
+ class_attribute :_attr_readonly, instance_writer: false
self._attr_readonly = []
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index c380b5c029..e9a8e90538 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -7,8 +7,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
- config_attribute :reflections
+ class_attribute :reflections
self.reflections = {}
end
@@ -18,36 +17,17 @@ module ActiveRecord
# and creates input fields for all of the attributes depending on their type
# and displays the associations to other objects.
#
- # MacroReflection class has info for AggregateReflection and AssociationReflection
- # classes.
+ # MacroReflection class has info for the AssociationReflection
+ # class.
module ClassMethods
def create_reflection(macro, name, options, active_record)
- case macro
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
- klass = options[:through] ? ThroughReflection : AssociationReflection
- reflection = klass.new(macro, name, options, active_record)
- when :composed_of
- reflection = AggregateReflection.new(macro, name, options, active_record)
- end
+ klass = options[:through] ? ThroughReflection : AssociationReflection
+ reflection = klass.new(macro, name, options, active_record)
self.reflections = self.reflections.merge(name => reflection)
reflection
end
- # Returns an array of AggregateReflection objects for all the aggregations in the class.
- def reflect_on_all_aggregations
- reflections.values.grep(AggregateReflection)
- end
-
- # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
- #
- # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
- #
- def reflect_on_aggregation(aggregation)
- reflection = reflections[aggregation]
- reflection if reflection.is_a?(AggregateReflection)
- end
-
# Returns an array of AssociationReflection objects for all the
# associations in the class. If you only want to reflect on a certain
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
@@ -79,24 +59,20 @@ module ActiveRecord
end
end
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
- # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ # Abstract base class for AssociationReflection. Objects of AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
# Returns the name of the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
attr_reader :name
# Returns the macro type.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
attr_reader :macro
# Returns the hash of options used for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
# <tt>has_many :clients</tt> returns +{}+
attr_reader :options
@@ -115,7 +91,6 @@ module ActiveRecord
# Returns the class for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
# <tt>has_many :clients</tt> returns the Client class
def klass
@klass ||= class_name.constantize
@@ -123,7 +98,6 @@ module ActiveRecord
# Returns the class name for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
def class_name
@class_name ||= (options[:class_name] || derive_class_name).to_s
@@ -149,16 +123,6 @@ module ActiveRecord
end
end
-
- # Holds all the meta-data about an aggregation as it was specified in the
- # Active Record class.
- class AggregateReflection < MacroReflection #:nodoc:
- def mapping
- mapping = options[:mapping] || [name, name]
- mapping.first.is_a?(Array) ? mapping : [mapping]
- end
- end
-
# Holds all the meta-data about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 05ced3299b..fe3aa00a74 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -300,7 +300,7 @@ module ActiveRecord
# Person.update(people.keys, people.values)
def update(id, attributes)
if id.is_a?(Array)
- id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
else
object = find(id)
object.update_attributes(attributes)
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 15f838a5ab..fb4388d4b2 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -2,20 +2,26 @@ require 'active_support/core_ext/object/blank'
module ActiveRecord
module Batches
- # Yields each record that was found by the find +options+. The find is
- # performed by find_in_batches with a batch size of 1000 (or as
+ # Looping through a collection of records from the database
+ # (using the +all+ method, for example) is very inefficient
+ # since it will try to instantiate all the objects at once.
+ #
+ # In that case, batch processing methods allow you to work
+ # with the records in batches, thereby greatly reducing memory consumption.
+ #
+ # The <tt>find_each</tt> method uses <tt>find_in_batches</tt> with a batch size of 1000 (or as
# specified by the <tt>:batch_size</tt> option).
#
- # Example:
+ # Person.all.find_each do |person|
+ # person.do_awesome_stuff
+ # end
#
# Person.where("age > 21").find_each do |person|
# person.party_all_night!
# end
#
- # Note: This method is only intended to use for batch processing of
- # large amounts of records that wouldn't fit in memory all at once. If
- # you just need to loop over less than 1000 records, it's probably
- # better just to use the regular find methods.
+ # You can also pass the <tt>:start</tt> option to specify
+ # an offset to control the starting point.
def find_each(options = {})
find_in_batches(options) do |records|
records.each { |record| yield record }
@@ -39,12 +45,15 @@ module ActiveRecord
# primary keys. You can't set the limit either, that's used to control
# the batch sizes.
#
- # Example:
- #
# Person.where("age > 21").find_in_batches do |group|
# sleep(50) # Make sure it doesn't get too crowded in there!
# group.each { |person| person.party_all_night! }
# end
+ #
+ # # Let's process the next 2000 records
+ # Person.all.find_in_batches(start: 2000, batch_size: 2000) do |group|
+ # group.each { |person| person.party_all_night! }
+ # end
def find_in_batches(options = {})
options.assert_valid_keys(:start, :batch_size)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 31d99f0192..22c3e6a324 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -16,9 +16,16 @@ module ActiveRecord
#
# Person.count(:age, distinct: true)
# # => counts the number of different age values
+ #
+ # Person.where("age > 26").count { |person| person.gender == 'female' }
+ # # => queries people where "age > 26" then count the loaded results filtering by gender
def count(column_name = nil, options = {})
- column_name, options = nil, column_name if column_name.is_a?(Hash)
- calculate(:count, column_name, options)
+ if block_given?
+ self.to_a.count { |item| yield item }
+ else
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
+ calculate(:count, column_name, options)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -52,9 +59,13 @@ module ActiveRecord
# +calculate+ for examples with options.
#
# Person.sum('age') # => 4562
+ # # => returns the total sum of all people's age
+ #
+ # Person.where('age > 100').sum { |person| person.age - 100 }
+ # # queries people where "age > 100" then perform a sum calculation with the block returns
def sum(*args)
if block_given?
- self.to_a.sum(*args) {|*block_args| yield(*block_args)}
+ self.to_a.sum(*args) { |item| yield item }
else
calculate(:sum, *args)
end
@@ -96,7 +107,8 @@ module ActiveRecord
relation = with_default_scope
if relation.equal?(self)
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
+
+ if has_include?(column_name)
construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
perform_calculation(operation, column_name, options)
@@ -118,7 +130,7 @@ module ActiveRecord
# Person.all.map(&:name)
#
# Pluck returns an <tt>Array</tt> of attribute values type-casted to match
- # the plucked column name, if it can be deduced. Plucking a SQL fragment
+ # the plucked column name, if it can be deduced. Plucking an SQL fragment
# returns String values by default.
#
# Examples:
@@ -144,21 +156,25 @@ module ActiveRecord
column_name = "#{table_name}.#{column_name}"
end
- result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
+ if has_include?(column_name)
+ construct_relation_for_association_calculations.pluck(column_name)
+ else
+ result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
- key = result.columns.first
- column = klass.column_types.fetch(key) {
- result.column_types.fetch(key) {
- Class.new { def type_cast(v); v; end }.new
+ key = result.columns.first
+ column = klass.column_types.fetch(key) {
+ result.column_types.fetch(key) {
+ Class.new { def type_cast(v); v; end }.new
+ }
}
- }
- result.map do |attributes|
- raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
+ result.map do |attributes|
+ raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
- value = klass.initialize_attributes(attributes).values.first
+ value = klass.initialize_attributes(attributes).values.first
- column.type_cast(value)
+ column.type_cast(value)
+ end
end
end
@@ -174,6 +190,10 @@ module ActiveRecord
private
+ def has_include?(column_name)
+ eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
+ end
+
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index f5fdf437bf..64dda4f35a 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -32,12 +32,12 @@ module ActiveRecord
protected
def method_missing(method, *args, &block)
- if Array.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
- to_a.send(method, *args, &block)
- elsif @klass.respond_to?(method)
+ if @klass.respond_to?(method)
::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
scoping { @klass.send(method, *args, &block) }
+ elsif Array.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
::ActiveRecord::Delegation.delegate method, :to => :arel
arel.send(method, *args, &block)
@@ -46,4 +46,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 4fedd33d64..c91758265b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -7,8 +7,6 @@ module ActiveRecord
# If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
# is an integer, find by id coerces its arguments using +to_i+.
#
- # ==== Examples
- #
# Person.find(1) # returns the object for ID = 1
# Person.find("1") # returns the object for ID = 1
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
@@ -49,7 +47,6 @@ module ActiveRecord
#
# Post.find_by name: 'Spartacus', rating: 4
# Post.find_by "published_at < ?", 2.weeks.ago
- #
def find_by(*args)
where(*args).take
end
@@ -64,8 +61,6 @@ module ActiveRecord
# order. The order will depend on the database implementation.
# If an order is supplied it will be respected.
#
- # Examples:
- #
# Person.take # returns an object fetched by SELECT * FROM people
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
# Person.where(["name LIKE '%?'", name]).take
@@ -82,12 +77,11 @@ module ActiveRecord
# Find the first record (or first N records if a parameter is supplied).
# If no order is defined it will order by primary key.
#
- # Examples:
- #
# Person.first # returns the first object fetched by SELECT * FROM people
# Person.where(["user_name = ?", user_name]).first
# Person.where(["user_name = :u", { :u => user_name }]).first
# Person.order("created_on DESC").offset(5).first
+ # Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
def first(limit = nil)
if limit
if order_values.empty? && primary_key
@@ -109,11 +103,18 @@ module ActiveRecord
# Find the last record (or last N records if a parameter is supplied).
# If no order is defined it will order by primary key.
#
- # Examples:
- #
# Person.last # returns the last object fetched by SELECT * FROM people
# Person.where(["user_name = ?", user_name]).last
# Person.order("created_on DESC").offset(5).last
+ # Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
+ #
+ # Take note that in that last case, the results are sorted in ascending order:
+ #
+ # [#<Person id:2>, #<Person id:3>, #<Person id:4>]
+ #
+ # and not:
+ #
+ # [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
if limit
if order_values.empty? && primary_key
@@ -132,7 +133,8 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Examples:
+ # Runs the query on the database and returns records with the used query
+ # methods.
#
# Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
# Person.where(["category IN (?)", categories]).limit(50).all
@@ -163,11 +165,10 @@ module ActiveRecord
# 'Jamie'</tt>), since it would be sanitized and then queried against
# the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
#
- # ==== Examples
# Person.exists?(5)
# Person.exists?('5')
- # Person.exists?(:name => "David")
# Person.exists?(['name LIKE ?', "%#{query}%"])
+ # Person.exists?(:name => "David")
# Person.exists?
def exists?(id = false)
id = id.id if ActiveRecord::Model === id
@@ -175,7 +176,7 @@ module ActiveRecord
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- relation = relation.except(:select, :order).select("1").limit(1)
+ relation = relation.except(:select, :order).select("1 AS one").limit(1)
case id
when Array, Hash
@@ -185,6 +186,8 @@ module ActiveRecord
end
connection.select_value(relation, "#{name} Exists", relation.bind_values)
+ rescue ThrowResult
+ false
end
protected
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 6a0cdd5917..cb8f903474 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -6,7 +6,7 @@ module ActiveRecord
if value.is_a?(Hash)
table = Arel::Table.new(column, engine)
- build_from_hash(engine, value, table)
+ value.map { |k,v| build(table[k.to_sym], v) }
else
column = column.to_s
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 19fe8155d9..529ddb5e31 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -194,10 +194,102 @@ module ActiveRecord
self
end
+ # Returns a new relation, which is the result of filtering the current relation
+ # according to the conditions in the arguments.
+ #
+ # #where accepts conditions in one of several formats. In the examples below, the resulting
+ # SQL is given as an illustration; the actual query generated may be different depending
+ # on the database adapter.
+ #
+ # === string
+ #
+ # A single string, without additional arguments, is passed to the query
+ # constructor as a SQL fragment, and used in the where clause of the query.
+ #
+ # Client.where("orders_count = '2'")
+ # # SELECT * from clients where orders_count = '2';
+ #
+ # Note that building your own string from user input may expose your application
+ # to injection attacks if not done properly. As an alternative, it is recommended
+ # to use one of the following methods.
+ #
+ # === array
+ #
+ # If an array is passed, then the first element of the array is treated as a template, and
+ # the remaining elements are inserted into the template to generate the condition.
+ # Active Record takes care of building the query to avoid injection attacks, and will
+ # convert from the ruby type to the database type where needed. Elements are inserted
+ # into the string in the order in which they appear.
+ #
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
+ # second element of the array. The names in the template are replaced with the corresponding
+ # values from the hash.
+ #
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # This can make for more readable code in complex queries.
+ #
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
+ # than the previous methods; you are responsible for ensuring that the values in the template
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
+ # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
+ #
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # If #where is called with multiple arguments, these are treated as if they were passed as
+ # the elements of a single array.
+ #
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # When using strings to specify conditions, you can use any operator available from
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
+ # dependencies on the underlying database. If your code is intended for general consumption,
+ # test with multiple database backends.
+ #
+ # === hash
+ #
+ # #where will also accept a hash condition, in which the keys are fields and the values
+ # are values to be searched for.
+ #
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
+ #
+ # User.where({ name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
+ #
+ # User.where({ name: ["Alice", "Bob"]})
+ # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
+ #
+ # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
+ # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
+ #
+ # === Joins
+ #
+ # If the relation is the result of a join, you may create a condition which uses any of the
+ # tables in the join. For string and array conditions, use the table name in the condition.
+ #
+ # User.joins(:posts).where("posts.created_at < ?", Time.now)
+ #
+ # For hash conditions, you can either use the table name in the key, or use a sub-hash.
+ #
+ # User.joins(:posts).where({ "posts.published" => true })
+ # User.joins(:posts).where({ :posts => { :published => true } })
+ #
+ # === empty condition
+ #
+ # If the condition returns true for blank?, then where is a no-op and returns the current relation.
def where(opts, *rest)
opts.blank? ? self : spawn.where!(opts, *rest)
end
+ # #where! is identical to #where, except that instead of returning a new relation, it adds
+ # the condition to the existing relation.
def where!(opts, *rest)
references!(PredicateBuilder.references(opts)) if Hash === opts
@@ -279,7 +371,7 @@ module ActiveRecord
# end
#
def none
- NullRelation.new(@klass, @table)
+ scoped.extending(NullRelation)
end
def readonly(value = true)
@@ -470,8 +562,7 @@ module ActiveRecord
when String, Array
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
- PredicateBuilder.build_from_hash(table.engine, attributes, table)
+ PredicateBuilder.build_from_hash(table.engine, opts, table)
else
[opts]
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 5530be3219..46f6c283e3 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -43,36 +43,6 @@ module ActiveRecord
end
end
- # Accepts a hash of SQL conditions and replaces those attributes
- # that correspond to a +composed_of+ relationship with their expanded
- # aggregate attribute values.
- # Given:
- # class Person < ActiveRecord::Base
- # composed_of :address, :class_name => "Address",
- # :mapping => [%w(address_street street), %w(address_city city)]
- # end
- # Then:
- # { :address => Address.new("813 abc st.", "chicago") }
- # # => { :address_street => "813 abc st.", :address_city => "chicago" }
- def expand_hash_conditions_for_aggregates(attrs)
- expanded_attrs = {}
- attrs.each do |attr, value|
- if aggregation = reflect_on_aggregation(attr.to_sym)
- mapping = aggregation.mapping
- mapping.each do |field_attr, aggregate_attr|
- if mapping.size == 1 && !value.respond_to?(aggregate_attr)
- expanded_attrs[field_attr] = value
- else
- expanded_attrs[field_attr] = value.send(aggregate_attr)
- end
- end
- else
- expanded_attrs[attr] = value
- end
- end
- expanded_attrs
- end
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
# { :name => "foo'bar", :group_id => 4 }
# # => "name='foo''bar' and group_id= 4"
@@ -88,8 +58,6 @@ module ActiveRecord
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
- attrs = expand_hash_conditions_for_aggregates(attrs)
-
table = Arel::Table.new(table_name).alias(default_table_name)
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
connection.visitor.accept b
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 7cbe2db408..a25a8d79bd 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -70,8 +70,8 @@ HEADER
@connection.tables.sort.each do |tbl|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
case ignored
- when String; tbl == ignored
- when Regexp; tbl =~ ignored
+ when String; remove_prefix_and_suffix(tbl) == ignored
+ when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
else
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
end
@@ -92,7 +92,7 @@ HEADER
pk = @connection.primary_key(table)
end
- tbl.print " create_table #{table.inspect}"
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
if columns.detect { |c| c.name == pk }
if pk != 'id'
tbl.print %Q(, :primary_key => "#{pk}")
@@ -175,7 +175,7 @@ HEADER
when BigDecimal
value.to_s
when Date, DateTime, Time
- "'" + value.to_s(:db) + "'"
+ "'#{value.to_s(:db)}'"
else
value.inspect
end
@@ -185,7 +185,7 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
statement_parts = [
- ('add_index ' + index.table.inspect),
+ ('add_index ' + remove_prefix_and_suffix(index.table).inspect),
index.columns.inspect,
(':name => ' + index.name.inspect),
]
@@ -206,5 +206,9 @@ HEADER
stream.puts
end
end
+
+ def remove_prefix_and_suffix(table)
+ table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index db833fc7f1..af51c803a7 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -8,7 +8,7 @@ module ActiveRecord
included do
# Stores the default scope for the class
- config_attribute :default_scopes
+ class_attribute :default_scopes, instance_writer: false
self.default_scopes = []
end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 41e3b92499..e8dd312a47 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,9 +1,21 @@
module ActiveRecord #:nodoc:
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :include_root_in_json, instance_accessor: false
+ self.include_root_in_json = true
+ end
+
# = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON
+ included do
+ singleton_class.class_eval do
+ remove_method :include_root_in_json
+ delegate :include_root_in_json, to: 'ActiveRecord::Model'
+ end
+ end
+
def serializable_hash(options = nil)
options = options.try(:clone) || {}
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 2e60521638..b833af64fe 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -18,8 +18,8 @@ module ActiveRecord #:nodoc:
# <id type="integer">1</id>
# <approved type="boolean">false</approved>
# <replies-count type="integer">0</replies-count>
- # <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
- # <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
+ # <bonus-time type="dateTime">2000-01-01T08:28:00+12:00</bonus-time>
+ # <written-on type="dateTime">2003-07-16T09:28:00+1200</written-on>
# <content>Have a nice day</content>
# <author-email-address>david@loudthinking.com</author-email-address>
# <parent-id></parent-id>
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index ce2ea85ef9..d13491502e 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -1,6 +1,10 @@
+require 'active_support/concern'
+require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/class/attribute'
+
module ActiveRecord
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
- # It's like a simple key/value store backed into your record when you don't care about being able to
+ # It's like a simple key/value store baked into your record when you don't care about being able to
# query that store outside the context of a single record.
#
# You can then declare accessors to this store that are then accessible just like any other attribute
@@ -13,9 +17,6 @@ module ActiveRecord
# You can set custom coder to encode/decode your serialized attributes to/from different formats.
# JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
#
- # String keys should be used for direct access to virtual attributes because of most of the coders do not
- # distinguish symbols and strings as keys.
- #
# Examples:
#
# class User < ActiveRecord::Base
@@ -23,34 +24,94 @@ module ActiveRecord
# end
#
# u = User.new(color: 'black', homepage: '37signals.com')
- # u.color # Accessor stored attribute
- # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor
+ # u.color # Accessor stored attribute
+ # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+ #
+ # # There is no difference between strings and symbols for accessing custom attributes
+ # u.settings[:country] # => 'Denmark'
+ # u.settings['country'] # => 'Denmark'
#
# # Add additional accessors to an existing store through store_accessor
# class SuperUser < User
# store_accessor :settings, :privileges, :servants
# end
+ #
+ # The stored attribute names can be retrieved using +stored_attributes+.
+ #
+ # User.stored_attributes[:settings] # [:color, :homepage]
module Store
extend ActiveSupport::Concern
+ included do
+ class_attribute :stored_attributes
+ self.stored_attributes = {}
+ end
+
module ClassMethods
def store(store_attribute, options = {})
- serialize store_attribute, options.fetch(:coder, Hash)
+ serialize store_attribute, IndifferentCoder.new(options[:coder])
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
end
def store_accessor(store_attribute, *keys)
- keys.flatten.each do |key|
+ keys = keys.flatten
+ keys.each do |key|
define_method("#{key}=") do |value|
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key.to_s] = value
- send("#{store_attribute}_will_change!")
+ initialize_store_attribute(store_attribute)
+ send(store_attribute)[key] = value
+ send :"#{store_attribute}_will_change!"
end
define_method(key) do
- send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
- send(store_attribute)[key.to_s]
+ initialize_store_attribute(store_attribute)
+ send(store_attribute)[key]
+ end
+ end
+
+ self.stored_attributes[store_attribute] = keys
+ end
+ end
+
+ private
+ def initialize_store_attribute(store_attribute)
+ case attribute = send(store_attribute)
+ when ActiveSupport::HashWithIndifferentAccess
+ # Already initialized. Do nothing.
+ when Hash
+ # Initialized as a Hash. Convert to indifferent access.
+ send :"#{store_attribute}=", attribute.with_indifferent_access
+ else
+ # Uninitialized. Set to an indifferent hash.
+ send :"#{store_attribute}=", ActiveSupport::HashWithIndifferentAccess.new
+ end
+ end
+
+ class IndifferentCoder
+ def initialize(coder_or_class_name)
+ @coder =
+ if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
+ coder_or_class_name
+ else
+ ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
end
+ end
+
+ def dump(obj)
+ @coder.dump self.class.as_indifferent_hash(obj)
+ end
+
+ def load(yaml)
+ self.class.as_indifferent_hash @coder.load(yaml)
+ end
+
+ def self.as_indifferent_hash(obj)
+ case obj
+ when ActiveSupport::HashWithIndifferentAccess
+ obj
+ when Hash
+ obj.with_indifferent_access
+ else
+ HashWithIndifferentAccess.new
end
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
new file mode 100644
index 0000000000..999b2ebc85
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -0,0 +1,109 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ module DatabaseTasks # :nodoc:
+ extend self
+
+ TASKS_PATTERNS = {
+ /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks,
+ /postgresql/ => ActiveRecord::Tasks::PostgreSQLDatabaseTasks,
+ /sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks
+ }
+ LOCAL_HOSTS = ['127.0.0.1', 'localhost']
+
+ def create(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).create
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't create database for #{configuration.inspect}"
+ end
+
+ def create_all
+ each_local_configuration { |configuration| create configuration }
+ end
+
+ def create_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ create configuration
+ }
+ ActiveRecord::Base.establish_connection environment
+ end
+
+ def drop(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).drop
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't drop #{configuration['database']}"
+ end
+
+ def drop_all
+ each_local_configuration { |configuration| drop configuration }
+ end
+
+ def drop_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ drop configuration
+ }
+ end
+
+ def charset_current(environment = Rails.env)
+ charset ActiveRecord::Base.configurations[environment]
+ end
+
+ def charset(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).charset
+ end
+
+ def purge(configuration)
+ class_for_adapter(configuration['adapter']).new(configuration).purge
+ end
+
+ def structure_dump(*arguments)
+ configuration = arguments.first
+ filename = arguments.delete_at 1
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_dump(filename)
+ end
+
+ def structure_load(*arguments)
+ configuration = arguments.first
+ filename = arguments.delete_at 1
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
+ end
+
+ private
+
+ def class_for_adapter(adapter)
+ key = TASKS_PATTERNS.keys.detect { |pattern| adapter[pattern] }
+ TASKS_PATTERNS[key]
+ end
+
+ def each_current_configuration(environment)
+ environments = [environment]
+ environments << 'test' if environment.development?
+
+ configurations = ActiveRecord::Base.configurations.values_at(*environments)
+ configurations.compact.each do |configuration|
+ yield configuration unless configuration['database'].blank?
+ end
+ end
+
+ def each_local_configuration
+ ActiveRecord::Base.configurations.each_value do |configuration|
+ next unless configuration['database']
+
+ if local_database?(configuration)
+ yield configuration
+ else
+ $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host."
+ end
+ end
+ end
+
+ def local_database?(configuration)
+ configuration['host'].in?(LOCAL_HOSTS) || configuration['host'].blank?
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
new file mode 100644
index 0000000000..b39cd2282f
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -0,0 +1,110 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class MySQLDatabaseTasks # :nodoc:
+
+ DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8'
+ DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci'
+ ACCESS_DENIED_ERROR = 1045
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create
+ establish_connection configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ establish_connection configuration
+ rescue error_class => error
+ raise error unless error.errno == ACCESS_DENIED_ERROR
+
+ $stdout.print error.error
+ establish_connection root_configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ connection.execute grant_statement.gsub(/\s+/, ' ').strip
+ establish_connection configuration
+ rescue error_class => error
+ $stderr.puts error.error
+ $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
+ $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['charset']
+ end
+
+ def drop
+ establish_connection configuration
+ connection.drop_database configuration['database']
+ end
+
+ def purge
+ establish_connection :test
+ connection.recreate_database configuration['database'], creation_options
+ end
+
+ def charset
+ connection.charset
+ end
+
+ def structure_dump(filename)
+ establish_connection configuration
+ File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ end
+
+ def structure_load(filename)
+ establish_connection(configuration)
+ connection.execute('SET foreign_key_checks = 0')
+ IO.read(filename).split("\n\n").each do |table|
+ connection.execute(table)
+ end
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def configuration_without_database
+ configuration.merge('database' => nil)
+ end
+
+ def creation_options
+ {
+ charset: (configuration['charset'] || DEFAULT_CHARSET),
+ collation: (configuration['collation'] || DEFAULT_COLLATION)
+ }
+ end
+
+ def error_class
+ case configuration['adapter']
+ when /jdbc/
+ require 'active_record/railties/jdbcmysql_error'
+ ArJdbcMySQL::Error
+ when /mysql2/
+ Mysql2::Error
+ else
+ Mysql::Error
+ end
+ end
+
+ def grant_statement
+ <<-SQL
+GRANT ALL PRIVILEGES ON #{configuration['database']}.*
+ TO '#{configuration['username']}'@'localhost'
+IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
+ SQL
+ end
+
+ def root_configuration_without_database
+ configuration_without_database.merge(
+ 'username' => 'root',
+ 'password' => root_password
+ )
+ end
+
+ def root_password
+ $stdout.print "Please provide the root password for your mysql installation\n>"
+ $stdin.gets.strip
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
new file mode 100644
index 0000000000..a210392e53
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -0,0 +1,81 @@
+require 'shellwords'
+
+module ActiveRecord
+ module Tasks # :nodoc:
+ class PostgreSQLDatabaseTasks # :nodoc:
+
+ DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8'
+
+ delegate :connection, :establish_connection, :clear_active_connections!,
+ to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create(master_established = false)
+ establish_master_connection unless master_established
+ connection.create_database configuration['database'],
+ configuration.merge('encoding' => encoding)
+ establish_connection configuration
+ end
+
+ def drop
+ establish_master_connection
+ connection.drop_database configuration['database']
+ end
+
+ def charset
+ connection.encoding
+ end
+
+ def purge
+ clear_active_connections!
+ drop
+ create true
+ end
+
+ def structure_dump(filename)
+ set_psql_env
+ search_path = configuration['schema_search_path']
+ unless search_path.blank?
+ search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
+ end
+
+ command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
+ raise 'Error dumping database' unless Kernel.system(command)
+
+ File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
+ end
+
+ def structure_load(filename)
+ set_psql_env
+ Kernel.system("psql -f #{filename} #{configuration['database']}")
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def encoding
+ configuration['encoding'] || DEFAULT_ENCODING
+ end
+
+ def establish_master_connection
+ establish_connection configuration.merge(
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+ end
+
+ def set_psql_env
+ ENV['PGHOST'] = configuration['host'] if configuration['host']
+ ENV['PGPORT'] = configuration['port'].to_s if configuration['port']
+ ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password']
+ ENV['PGUSER'] = configuration['username'].to_s if configuration['username']
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
new file mode 100644
index 0000000000..da01058a82
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -0,0 +1,55 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SQLiteDatabaseTasks # :nodoc:
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration, root = Rails.root)
+ @configuration, @root = configuration, root
+ end
+
+ def create
+ if File.exist?(configuration['database'])
+ $stderr.puts "#{configuration['database']} already exists"
+ return
+ end
+
+ establish_connection configuration
+ connection
+ end
+
+ def drop
+ require 'pathname'
+ path = Pathname.new configuration['database']
+ file = path.absolute? ? path.to_s : File.join(root, path)
+
+ FileUtils.rm(file) if File.exist?(file)
+ end
+ alias :purge :drop
+
+ def charset
+ connection.encoding
+ end
+
+ def structure_dump(filename)
+ dbfile = configuration['database']
+ `sqlite3 #{dbfile} .schema > #{filename}`
+ end
+
+ def structure_load(filename)
+ dbfile = configuration['database']
+ `sqlite3 #{dbfile} < "#{filename}"`
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def root
+ @root
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index fcaa4b74a6..c7a6c37d50 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -8,7 +8,7 @@ module ActiveRecord
# Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
def teardown
- SQLCounter.log.clear
+ SQLCounter.clear_log
end
def assert_date_from_db(expected, actual, message = nil)
@@ -22,47 +22,57 @@ module ActiveRecord
end
def assert_sql(*patterns_to_match)
- SQLCounter.log = []
+ SQLCounter.clear_log
yield
- SQLCounter.log
+ SQLCounter.log_all
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
- failed_patterns << pattern unless SQLCounter.log.any?{ |sql| pattern === sql }
+ failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql }
end
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
- def assert_queries(num = 1)
- SQLCounter.log = []
+ def assert_queries(num = 1, options = {})
+ ignore_none = options.fetch(:ignore_none) { num == :any }
+ SQLCounter.clear_log
yield
ensure
- assert_equal num, SQLCounter.log.size, "#{SQLCounter.log.size} instead of #{num} queries were executed.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
+ the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
+ if num == :any
+ assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed."
+ else
+ mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}"
+ assert_equal num, the_log.size, mesg
+ end
end
def assert_no_queries(&block)
- prev_ignored_sql = SQLCounter.ignored_sql
- SQLCounter.ignored_sql = []
- assert_queries(0, &block)
- ensure
- SQLCounter.ignored_sql = prev_ignored_sql
+ assert_queries(0, :ignore_none => true, &block)
end
end
class SQLCounter
class << self
- attr_accessor :ignored_sql, :log
+ attr_accessor :ignored_sql, :log, :log_all
+ def clear_log; self.log = []; self.log_all = []; end
end
- self.log = []
+ self.clear_log
self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
# FIXME: this needs to be refactored so specific database can add their own
- # ignored SQL. This ignored SQL is for Oracle.
- ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
-
+ # ignored SQL, or better yet, use a different notification for the queries
+ # instead examining the SQL content.
+ oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
+ mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/]
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im]
+
+ [oracle_ignored, mysql_ignored, postgresql_ignored].each do |db_ignored_sql|
+ ignored_sql.concat db_ignored_sql
+ end
attr_reader :ignore
@@ -75,8 +85,10 @@ module ActiveRecord
# FIXME: this seems bad. we should probably have a better way to indicate
# the query was cached
- return if 'CACHE' == values[:name] || ignore =~ sql
- self.class.log << sql
+ return if 'CACHE' == values[:name]
+
+ self.class.log_all << sql
+ self.class.log << sql unless ignore =~ sql
end
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index c717fdea47..e5b7a6bfba 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,6 +1,11 @@
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :record_timestamps, instance_accessor: false
+ self.record_timestamps = true
+ end
+
# = Active Record Timestamp
#
# Active Record automatically timestamps create and update operations if the
@@ -33,8 +38,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :record_timestamps, :instance_writer => true
- self.record_timestamps = true
+ config_attribute :record_timestamps, instance_writer: true
end
def initialize_dup(other)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 30e1035300..9cb9b4627b 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -329,7 +329,8 @@ module ActiveRecord
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
if @_start_transaction_state[:level] < 1
restore_state = remove_instance_variable(:@_start_transaction_state)
- @attributes = @attributes.dup if @attributes.frozen?
+ was_frozen = @attributes.frozen?
+ @attributes = @attributes.dup if was_frozen
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state.has_key?(:id)
@@ -338,6 +339,7 @@ module ActiveRecord
@attributes.delete(self.class.primary_key)
@attributes_cache.delete(self.class.primary_key)
end
+ @attributes.freeze if was_frozen
end
end
end
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 69dfd2503e..1199be68eb 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -1,6 +1,6 @@
module ActiveRecord
- class Base
- def self.fake_connection(config)
+ module ConnectionHandling
+ def fake_connection(config)
ConnectionAdapters::FakeAdapter.new nil, logger
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 684c7f5929..276c499276 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -4,6 +4,13 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def setup
super
@connection = ActiveRecord::Model.connection
+ @connection.extend(LogIntercepter)
+ @connection.intercepted = true
+ end
+
+ def teardown
+ @connection.intercepted = false
+ @connection.logged = []
end
def test_no_automatic_reconnection_after_timeout
@@ -45,6 +52,26 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_logs_name_structure_dump
+ @connection.structure_dump
+ assert_equal "SCHEMA", @connection.logged[0][1]
+ assert_equal "SCHEMA", @connection.logged[2][1]
+ end
+
+ def test_logs_name_show_variable
+ @connection.show_variable 'foo'
+ assert_equal "SCHEMA", @connection.logged[0][1]
+ end
+
+ def test_logs_name_rename_column_sql
+ @connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
+ @connection.logged = []
+ @connection.send(:rename_column_sql, 'bar_baz', 'foo', 'foo2')
+ assert_equal "SCHEMA", @connection.logged[0][1]
+ ensure
+ @connection.execute "DROP TABLE `bar_baz`"
+ end
+
private
def run_without_connection
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 4baec749ff..f6e168caf2 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -8,12 +8,23 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
+ @connection.extend(LogIntercepter)
+ @connection.intercepted = true
+ end
+
+ def teardown
+ @connection.intercepted = false
+ @connection.logged = []
end
def test_encoding
assert_not_nil @connection.encoding
end
+ def test_default_client_min_messages
+ assert_equal "warning", @connection.client_min_messages
+ end
+
# Ensure, we can set connection params using the example of Generic
# Query Optimizer (geqo). It is 'on' per default.
def test_connection_options
@@ -25,5 +36,42 @@ module ActiveRecord
expect = NonExistentTable.connection.query('show geqo').first.first
assert_equal 'off', expect
end
+
+ def test_tables_logs_name
+ @connection.tables('hello')
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_indexes_logs_name
+ @connection.indexes('items', 'hello')
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_table_exists_logs_name
+ @connection.table_exists?('items')
+ assert_equal 'SCHEMA', @connection.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]
+ end
+
+ def test_current_database_logs_name
+ @connection.current_database
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_encoding_logs_name
+ @connection.encoding
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
+ def test_schema_names_logs_name
+ @connection.schema_names
+ assert_equal 'SCHEMA', @connection.logged[0][1]
+ end
+
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 34660577da..a4d9286d52 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -27,6 +27,9 @@ end
class PostgresqlTimestampWithZone < ActiveRecord::Base
end
+class PostgresqlUUID < ActiveRecord::Base
+end
+
class PostgresqlDataTypeTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -61,6 +64,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@first_oid = PostgresqlOid.find(1)
@connection.execute("INSERT INTO postgresql_timestamp_with_zones (time) VALUES ('2010-01-01 10:00:00-1')")
+
+ @connection.execute("INSERT INTO postgresql_uuids (guid, compact_guid) VALUES('d96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')")
+ @first_uuid = PostgresqlUUID.find(1)
end
def test_data_type_of_array_types
@@ -100,6 +106,10 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
+ def test_data_type_of_uuid_types
+ assert_equal :uuid, @first_uuid.column_for_attribute(:guid).type
+ end
+
def test_array_values
assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter
assert_equal '{foo,bar,baz}', @first_array.nicknames
@@ -143,6 +153,11 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
end
+ def test_uuid_values
+ assert_equal 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', @first_uuid.guid
+ assert_equal 'f06c7150-96c1-012f-1317-64ce8f32c6d8', @first_uuid.compact_guid
+ end
+
def test_bit_string_values
assert_equal '00010101', @first_bit_string.bit_string
assert_equal '00010101', @first_bit_string.bit_string_varying
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 8a7f44d0a3..5e947799cc 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -20,6 +20,14 @@ module ActiveRecord
number integer
)
eosql
+
+ @conn.extend(LogIntercepter)
+ @conn.intercepted = true
+ end
+
+ def teardown
+ @conn.intercepted = false
+ @conn.logged = []
end
def test_column_types
@@ -232,13 +240,23 @@ module ActiveRecord
end
def test_tables_logs_name
- name = "hello"
- assert_logged [[name, []]] do
- @conn.tables(name)
+ assert_logged [['SCHEMA', []]] do
+ @conn.tables('hello')
assert_not_nil @conn.logged.first.shift
end
end
+ def test_indexes_logs_name
+ assert_logged [["PRAGMA index_list(\"items\")", 'SCHEMA', []]] do
+ @conn.indexes('items', 'hello')
+ end
+ end
+
+ def test_table_exists_logs_name
+ assert @conn.table_exists?('items')
+ assert_equal 'SCHEMA', @conn.logged[0][1]
+ end
+
def test_columns
columns = @conn.columns('items').sort_by { |x| x.name }
assert_equal 2, columns.length
@@ -274,7 +292,6 @@ module ActiveRecord
end
def test_indexes_logs
- intercept_logs_on @conn
assert_difference('@conn.logged.length') do
@conn.indexes('items')
end
@@ -326,21 +343,10 @@ module ActiveRecord
private
def assert_logged logs
- intercept_logs_on @conn
yield
assert_equal logs, @conn.logged
end
- def intercept_logs_on ctx
- @conn.extend(Module.new {
- attr_accessor :logged
- def log sql, name, binds = []
- @logged << [sql, name, binds]
- yield
- end
- })
- @conn.logged = []
- end
end
end
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
deleted file mode 100644
index 5bd8f76ba2..0000000000
--- a/activerecord/test/cases/aggregations_test.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-require "cases/helper"
-require 'models/customer'
-require 'active_support/core_ext/exception'
-
-class AggregationsTest < ActiveRecord::TestCase
- fixtures :customers
-
- def test_find_single_value_object
- assert_equal 50, customers(:david).balance.amount
- assert_kind_of Money, customers(:david).balance
- assert_equal 300, customers(:david).balance.exchange_to("DKK").amount
- end
-
- def test_find_multiple_value_object
- assert_equal customers(:david).address_street, customers(:david).address.street
- assert(
- customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country))
- )
- end
-
- def test_change_single_value_object
- customers(:david).balance = Money.new(100)
- customers(:david).save
- assert_equal 100, customers(:david).reload.balance.amount
- end
-
- def test_immutable_value_objects
- customers(:david).balance = Money.new(100)
- assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } }
- end
-
- def test_inferred_mapping
- assert_equal "35.544623640962634", customers(:david).gps_location.latitude
- assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
-
- customers(:david).gps_location = GpsLocation.new("39x-110")
-
- assert_equal "39", customers(:david).gps_location.latitude
- assert_equal "-110", customers(:david).gps_location.longitude
-
- customers(:david).save
-
- customers(:david).reload
-
- assert_equal "39", customers(:david).gps_location.latitude
- assert_equal "-110", customers(:david).gps_location.longitude
- end
-
- def test_reloaded_instance_refreshes_aggregations
- assert_equal "35.544623640962634", customers(:david).gps_location.latitude
- assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
-
- Customer.update_all("gps_location = '24x113'")
- customers(:david).reload
- assert_equal '24x113', customers(:david)['gps_location']
-
- assert_equal GpsLocation.new('24x113'), customers(:david).gps_location
- end
-
- def test_gps_equality
- assert_equal GpsLocation.new('39x110'), GpsLocation.new('39x110')
- end
-
- def test_gps_inequality
- assert_not_equal GpsLocation.new('39x110'), GpsLocation.new('39x111')
- end
-
- def test_allow_nil_gps_is_nil
- assert_nil customers(:zaphod).gps_location
- end
-
- def test_allow_nil_gps_set_to_nil
- customers(:david).gps_location = nil
- customers(:david).save
- customers(:david).reload
- assert_nil customers(:david).gps_location
- end
-
- def test_allow_nil_set_address_attributes_to_nil
- customers(:zaphod).address = nil
- assert_nil customers(:zaphod).attributes[:address_street]
- assert_nil customers(:zaphod).attributes[:address_city]
- assert_nil customers(:zaphod).attributes[:address_country]
- end
-
- def test_allow_nil_address_set_to_nil
- customers(:zaphod).address = nil
- customers(:zaphod).save
- customers(:zaphod).reload
- assert_nil customers(:zaphod).address
- end
-
- def test_nil_raises_error_when_allow_nil_is_false
- assert_raise(NoMethodError) { customers(:david).balance = nil }
- end
-
- def test_allow_nil_address_loaded_when_only_some_attributes_are_nil
- customers(:zaphod).address_street = nil
- customers(:zaphod).save
- customers(:zaphod).reload
- assert_kind_of Address, customers(:zaphod).address
- assert_nil customers(:zaphod).address.street
- end
-
- def test_nil_assignment_results_in_nil
- customers(:david).gps_location = GpsLocation.new('39x111')
- assert_not_nil customers(:david).gps_location
- customers(:david).gps_location = nil
- assert_nil customers(:david).gps_location
- end
-
- def test_nil_return_from_converter_is_respected_when_allow_nil_is_true
- customers(:david).non_blank_gps_location = ""
- customers(:david).save
- customers(:david).reload
- assert_nil customers(:david).non_blank_gps_location
- end
-
- def test_nil_return_from_converter_results_in_failure_when_allow_nil_is_false
- assert_raises(NoMethodError) do
- customers(:barney).gps_location = ""
- end
- end
-
- def test_do_not_run_the_converter_when_nil_was_set
- customers(:david).non_blank_gps_location = nil
- assert_nil Customer.gps_conversion_was_run
- end
-
- def test_custom_constructor
- assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s
- assert_kind_of Fullname, customers(:barney).fullname
- end
-
- def test_custom_converter
- customers(:barney).fullname = 'Barnoit Gumbleau'
- assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s
- assert_kind_of Fullname, customers(:barney).fullname
- end
-end
-
-class OverridingAggregationsTest < ActiveRecord::TestCase
- class Name; end
- class DifferentName; end
-
- class Person < ActiveRecord::Base
- composed_of :composed_of, :mapping => %w(person_first_name first_name)
- end
-
- class DifferentPerson < Person
- composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name)
- end
-
- def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited
- assert_not_equal Person.reflect_on_aggregation(:composed_of),
- DifferentPerson.reflect_on_aggregation(:composed_of)
- end
-end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 08467900f9..31b11ee334 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -29,11 +29,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
:developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors
- def setup
- # preheat table existence caches
- Comment.find_by_id(1)
- end
-
def test_eager_with_has_one_through_join_model_with_conditions_on_the_through
member = Member.scoped(:includes => :favourite_club).find(members(:some_other_guy).id)
assert_nil member.favourite_club
@@ -983,7 +978,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.scoped(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').all
end
assert_equal posts(:welcome, :thinking), posts
-
end
def test_eager_loading_with_conditions_on_string_joined_table_preloads
@@ -998,7 +992,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
-
end
def test_eager_loading_with_select_on_joined_table_preloads
@@ -1010,8 +1003,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_loading_with_conditions_on_join_model_preloads
- Author.columns
-
authors = assert_queries(2) do
Author.scoped(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").all
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 ed1caa2ef5..9d693bae0c 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
@@ -817,11 +817,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
# clear cache possibly created by other tests
david.projects.reset_column_information
- assert_queries(1) { david.projects.columns; david.projects.columns }
+ assert_queries(:any) { david.projects.columns }
+ assert_no_queries { david.projects.columns }
## and again to verify that reset_column_information clears the cache correctly
david.projects.reset_column_information
- assert_queries(1) { david.projects.columns; david.projects.columns }
+
+ assert_queries(:any) { david.projects.columns }
+ assert_no_queries { david.projects.columns }
end
def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 8b384c2513..b8481175b2 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -936,10 +936,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, summit.client_of
end
- def test_deleting_type_mismatch
+ def test_deleting_by_fixnum_id
david = Developer.find(1)
- david.projects.reload
- assert_raise(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) }
+
+ assert_difference 'david.projects.count', -1 do
+ assert_equal 1, david.projects.delete(1).size
+ end
+
+ assert_equal 1, david.projects.size
+ end
+
+ def test_deleting_by_string_id
+ david = Developer.find(1)
+
+ assert_difference 'david.projects.count', -1 do
+ assert_equal 1, david.projects.delete('1').size
+ end
+
+ assert_equal 1, david.projects.size
end
def test_deleting_self_type_mismatch
@@ -1344,7 +1358,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_equal author.essays, Essay.where(writer_id: "David")
-
end
def test_has_many_custom_primary_key
@@ -1424,7 +1437,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
:group => "#{Namespaced::Firm.table_name}.id"
).find firm.id
assert_equal 1, stats.num_clients.to_i
-
ensure
ActiveRecord::Base.store_full_sti_class = old
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index ecc676f300..783b83631c 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -578,7 +578,27 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_deleting_junk_from_has_many_through_should_raise_type_mismatch
- assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") }
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) }
+ end
+
+ def test_deleting_by_fixnum_id_from_has_many_through
+ post = posts(:thinking)
+
+ assert_difference 'post.tags.count', -1 do
+ assert_equal 1, post.tags.delete(1).size
+ end
+
+ assert_equal 0, post.tags.size
+ end
+
+ def test_deleting_by_string_id_from_has_many_through
+ post = posts(:thinking)
+
+ assert_difference 'post.tags.count', -1 do
+ assert_equal 1, post.tags.delete('1').size
+ end
+
+ assert_equal 0, post.tags.size
end
def test_has_many_through_sum_uses_calculations
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 98c38535a6..f4c40b8b97 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -15,6 +15,7 @@ module ActiveRecord
def self.active_record_super; Base; end
def self.base_class; self; end
+ extend ActiveRecord::Configuration
include ActiveRecord::AttributeMethods
def self.define_attribute_methods
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 619fb881fa..2fee553cef 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -888,22 +888,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
end
- def test_multiparameter_assignment_of_aggregation
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- assert_equal address, customer.address
- end
-
- def test_multiparameter_assignment_of_aggregation_out_of_order
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
- customer.attributes = attributes
- assert_equal address, customer.address
- end
-
def test_multiparameter_assignment_of_aggregation_with_missing_values
ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
customer = Customer.new
@@ -914,14 +898,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("address", ex.errors[0].attribute)
end
- def test_multiparameter_assignment_of_aggregation_with_blank_values
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- assert_equal Address.new(nil, "The City", "The Country"), customer.address
- end
-
def test_multiparameter_assignment_of_aggregation_with_large_index
ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
customer = Customer.new
@@ -1021,26 +997,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("c", duped_topic.title)
end
- def test_dup_with_aggregate_of_same_name_as_attribute
- dev = DeveloperWithAggregate.find(1)
- assert_kind_of DeveloperSalary, dev.salary
-
- dup = nil
- assert_nothing_raised { dup = dev.dup }
- assert_kind_of DeveloperSalary, dup.salary
- assert_equal dev.salary.amount, dup.salary.amount
- assert !dup.persisted?
-
- # test if the attributes have been dupd
- original_amount = dup.salary.amount
- dev.salary.amount = 1
- assert_equal original_amount, dup.salary.amount
-
- assert dup.save
- assert dup.persisted?
- assert_not_equal dup.id, dev.id
- end
-
def test_dup_does_not_copy_associations
author = authors(:david)
assert_not_equal [], author.posts
@@ -1309,6 +1265,15 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal({ :foo => :bar }, t.content_before_type_cast)
end
+ def test_serialized_attribute_calling_dup_method
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = "topics"
+ klass.serialize :content, JSON
+
+ t = klass.new(:content => { :foo => :bar }).dup
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ end
+
def test_serialized_attribute_declared_in_subclass
hash = { 'important1' => 'value1', 'important2' => 'value2' }
important_topic = ImportantTopic.create("important" => hash)
@@ -1514,6 +1479,8 @@ class BasicsTest < ActiveRecord::TestCase
after_seq = Joke.sequence_name
assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
+ ensure
+ Joke.reset_sequence_name
end
def test_dont_clear_inheritnce_column_when_setting_explicitly
@@ -1926,7 +1893,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_cache_key_format_for_existing_record_with_nil_updated_at
dev = Developer.first
- dev.update_attribute(:updated_at, nil)
+ dev.update_column(:updated_at, nil)
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 041f8ffb7c..f748b897ee 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -376,6 +376,22 @@ class CalculationsTest < ActiveRecord::TestCase
Company.where(:type => "Firm").from('companies').count(:type)
end
+ def test_count_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.count, accounts.count { true }
+ assert_equal 0, accounts.count { false }
+ assert_equal Account.where('credit_limit > 50').size, accounts.count { |account| account.credit_limit > 50 }
+ assert_equal Account.count, Account.count { true }
+ assert_equal 0, Account.count { false }
+ end
+
+ def test_sum_with_block_acts_as_array
+ accounts = Account.where('id > 0')
+ assert_equal Account.sum(:credit_limit), accounts.sum { |account| account.credit_limit }
+ assert_equal Account.sum(:credit_limit) + Account.count, accounts.sum{ |account| account.credit_limit + 1 }
+ assert_equal 0, accounts.sum { |account| 0 }
+ end
+
def test_sum_with_from_option
assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit)
assert_equal Account.where("credit_limit > 50").sum(:credit_limit),
@@ -404,6 +420,40 @@ class CalculationsTest < ActiveRecord::TestCase
Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit)
end
+ def test_maximum_with_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+
+ # TODO: Investigate why PG isn't being typecast
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal "7", Company.includes(:contracts).maximum(:developer_id)
+ else
+ assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
+ end
+ end
+
+ def test_minimum_with_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+
+ # TODO: Investigate why PG isn't being typecast
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal "7", Company.includes(:contracts).minimum(:developer_id)
+ else
+ assert_equal 7, Company.includes(:contracts).minimum(:developer_id)
+ end
+ end
+
+ def test_sum_with_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+
+ # TODO: Investigate why PG isn't being typecast
+ if current_adapter?(:MysqlAdapter) || current_adapter?(:PostgreSQLAdapter)
+ assert_equal "7", Company.includes(:contracts).sum(:developer_id)
+ else
+ assert_equal 7, Company.includes(:contracts).sum(:developer_id)
+ end
+ end
+
+
def test_from_option_with_specified_index
if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all)
@@ -461,6 +511,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [c.id], Company.joins(:contracts).pluck(:id)
end
+ def test_pluck_if_table_included
+ c = Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ assert_equal [c.id], Company.includes(:contracts).where("contracts.id" => c.contracts.first).pluck(:id)
+ end
+
def test_pluck_not_auto_table_name_prefix_if_column_joined
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
assert_equal [7], Company.joins(:contracts).pluck(:developer_id)
@@ -484,4 +539,11 @@ class CalculationsTest < ActiveRecord::TestCase
def test_plucks_with_ids
assert_equal Company.all.map(&:id).sort, Company.ids.sort
end
+
+ def test_pluck_not_auto_table_name_prefix_if_column_included
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ ids = Company.includes(:contracts).pluck(:developer_id)
+ assert_equal Company.count, ids.length
+ assert_equal [7], ids.compact
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 7690769226..deeef3a3fd 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -426,11 +426,13 @@ class CallbacksTest < ActiveRecord::TestCase
def test_before_destroy_returning_false
david = ImmutableDeveloper.find(1)
assert !david.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
assert_not_nil ImmutableDeveloper.find_by_id(1)
someone = CallbackCancellationDeveloper.find(1)
someone.cancel_before_destroy = true
assert !someone.destroy
+ assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
assert !someone.after_destroy_called
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index a44b49466f..bd2fbaa7db 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -136,12 +136,6 @@ module ActiveRecord
smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
assert_equal :integer, smallint_column.type
end
-
- def test_uuid_column_should_map_to_string
- oid = PostgreSQLAdapter::OID::Identity.new
- uuid_column = PostgreSQLColumn.new('unique_id', nil, oid, "uuid")
- assert_equal :string, uuid_column.type
- end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index dc99ac665c..17cb447105 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -7,12 +7,11 @@ module ActiveRecord
@handler = ConnectionHandler.new
@handler.establish_connection 'america', Base.connection_pool.spec
@klass = Class.new do
+ include Model::Tag
def self.name; 'america'; end
- class << self
- alias active_record_super superclass
- end
end
@subklass = Class.new(@klass) do
+ include Model::Tag
def self.name; 'north america'; end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 6871e628aa..8287b35aaf 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -124,7 +124,7 @@ module ActiveRecord
@pool.checkout
@pool.checkout
@pool.checkout
- @pool.timeout = 0
+ @pool.dead_connection_timeout = 0
connections = @pool.connections.dup
@@ -137,7 +137,7 @@ module ActiveRecord
@pool.checkout
@pool.checkout
@pool.checkout
- @pool.timeout = 0
+ @pool.dead_connection_timeout = 0
connections = @pool.connections.dup
connections.each do |conn|
@@ -213,13 +213,8 @@ module ActiveRecord
# Thus this test prepares waiting threads and then trickles in
# available connections slowly, ensuring the wakeup order is
# correct in this case.
- #
- # Try a few times since it might work out just by chance.
def test_checkout_fairness
- 4.times { setup; do_checkout_fairness }
- end
-
- def do_checkout_fairness
+ @pool.instance_variable_set(:@size, 10)
expected = (1..@pool.size).to_a.freeze
# check out all connections so our threads start out waiting
conns = expected.map { @pool.checkout }
@@ -230,7 +225,7 @@ module ActiveRecord
threads = expected.map do |i|
t = Thread.new {
begin
- conn = @pool.checkout # never checked back in
+ @pool.checkout # never checked back in
mutex.synchronize { order << i }
rescue => e
mutex.synchronize { errors << e }
@@ -256,13 +251,7 @@ module ActiveRecord
# group2. Enough connections are checked in to wakeup all
# group1 threads, and the fact that only group1 and no group2
# threads acquired a connection is enforced.
- #
- # Try a few times since it might work out just by chance.
def test_checkout_fairness_by_group
- 4.times { setup; do_checkout_fairness_by_group }
- end
-
- def do_checkout_fairness_by_group
@pool.instance_variable_set(:@size, 10)
# take all the connections
conns = (1..10).map { @pool.checkout }
@@ -273,7 +262,7 @@ module ActiveRecord
make_thread = proc do |i|
t = Thread.new {
begin
- conn = @pool.checkout # never checked back in
+ @pool.checkout # never checked back in
mutex.synchronize { successes << i }
rescue => e
mutex.synchronize { errors << e }
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index e6cb1b9521..673a2b2b88 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -9,6 +9,7 @@ module ActiveRecord
end
def test_url_host_no_db
+ skip "only if mysql is available" unless defined?(MysqlAdapter)
spec = resolve 'mysql://foo?encoding=utf8'
assert_equal({
:adapter => "mysql",
@@ -18,6 +19,7 @@ module ActiveRecord
end
def test_url_host_db
+ skip "only if mysql is available" unless defined?(MysqlAdapter)
spec = resolve 'mysql://foo/bar?encoding=utf8'
assert_equal({
:adapter => "mysql",
@@ -27,6 +29,7 @@ module ActiveRecord
end
def test_url_port
+ skip "only if mysql is available" unless defined?(MysqlAdapter)
spec = resolve 'mysql://foo:123?encoding=utf8'
assert_equal({
:adapter => "mysql",
diff --git a/activerecord/test/cases/database_tasks_test.rb b/activerecord/test/cases/database_tasks_test.rb
new file mode 100644
index 0000000000..5f36b2c841
--- /dev/null
+++ b/activerecord/test/cases/database_tasks_test.rb
@@ -0,0 +1,275 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module DatabaseTasksSetupper
+ def setup
+ @mysql_tasks, @postgresql_tasks, @sqlite_tasks = stub, stub, stub
+ ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
+ ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks
+ ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+ end
+ end
+
+ ADAPTERS_TASKS = {
+ :mysql => :mysql_tasks,
+ :mysql2 => :mysql_tasks,
+ :postgresql => :postgresql_tasks,
+ :sqlite3 => :sqlite_tasks
+ }
+
+ class DatabaseTasksCreateTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_create") do
+ eval("@#{v}").expects(:create)
+ ActiveRecord::Tasks::DatabaseTasks.create 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksCreateAllTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {'development' => {'database' => 'my-db'}}
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations['development'].merge!('database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_ignores_remote_databases
+ @configurations['development'].merge!('host' => 'my.server.tld')
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_warning_for_remote_databases
+ @configurations['development'].merge!('host' => 'my.server.tld')
+
+ $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_local_ip
+ @configurations['development'].merge!('host' => '127.0.0.1')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_local_host
+ @configurations['development'].merge!('host' => 'localhost')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+
+ def test_creates_configurations_with_blank_hosts
+ @configurations['development'].merge!('host' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create)
+
+ ActiveRecord::Tasks::DatabaseTasks.create_all
+ end
+ end
+
+ class DatabaseTasksCreateCurrentTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ 'development' => {'database' => 'dev-db'},
+ 'test' => {'database' => 'test-db'},
+ 'production' => {'database' => 'prod-db'}
+ }
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'prod-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('production')
+ )
+ end
+
+ def test_creates_test_database_when_environment_is_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'dev-db')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'test-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+
+ def test_establishes_connection_for_the_given_environment
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true
+
+ ActiveRecord::Base.expects(:establish_connection).with('development')
+
+ ActiveRecord::Tasks::DatabaseTasks.create_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+ end
+
+ class DatabaseTasksDropTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_drop") do
+ eval("@#{v}").expects(:drop)
+ ActiveRecord::Tasks::DatabaseTasks.drop 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksDropAllTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {:development => {'database' => 'my-db'}}
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_ignores_configurations_without_databases
+ @configurations[:development].merge!('database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_ignores_remote_databases
+ @configurations[:development].merge!('host' => 'my.server.tld')
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).never
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_warning_for_remote_databases
+ @configurations[:development].merge!('host' => 'my.server.tld')
+
+ $stderr.expects(:puts).with('This task only modifies local databases. my-db is on a remote host.')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_local_ip
+ @configurations[:development].merge!('host' => '127.0.0.1')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_local_host
+ @configurations[:development].merge!('host' => 'localhost')
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+
+ def test_creates_configurations_with_blank_hosts
+ @configurations[:development].merge!('host' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
+ end
+ end
+
+ class DatabaseTasksDropCurrentTest < ActiveRecord::TestCase
+ def setup
+ @configurations = {
+ 'development' => {'database' => 'dev-db'},
+ 'test' => {'database' => 'test-db'},
+ 'production' => {'database' => 'prod-db'}
+ }
+
+ ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ end
+
+ def test_creates_current_environment_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'prod-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new('production')
+ )
+ end
+
+ def test_creates_test_database_when_environment_is_database
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'dev-db')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'test-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop_current(
+ ActiveSupport::StringInquirer.new('development')
+ )
+ end
+ end
+
+
+ class DatabaseTasksPurgeTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_purge") do
+ eval("@#{v}").expects(:purge)
+ ActiveRecord::Tasks::DatabaseTasks.purge 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksCharsetTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_charset") do
+ eval("@#{v}").expects(:charset)
+ ActiveRecord::Tasks::DatabaseTasks.charset 'adapter' => k
+ end
+ end
+ end
+
+ class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_structure_dump") do
+ eval("@#{v}").expects(:structure_dump).with("awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => k}, "awesome-file.sql")
+ end
+ end
+ end
+
+ class DatabaseTasksStructureLoadTest < ActiveRecord::TestCase
+ include DatabaseTasksSetupper
+
+ ADAPTERS_TASKS.each do |k, v|
+ define_method("test_#{k}_structure_load") do
+ eval("@#{v}").expects(:structure_load).with("awesome-file.sql")
+ ActiveRecord::Tasks::DatabaseTasks.structure_load({'adapter' => k}, "awesome-file.sql")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
index 77a609f49a..7c108b983e 100644
--- a/activerecord/test/cases/deprecated_dynamic_methods_test.rb
+++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -45,32 +45,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert_equal [], Topic.find_all_by_title("The First Topic!!")
end
- def test_find_all_by_one_attribute_that_is_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance(balance)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_that_are_both_aggregates
- balance = customers(:david).balance
- address = customers(:david).address
- assert_kind_of Money, balance
- assert_kind_of Address, address
- found_customers = Customer.find_all_by_balance_and_address(balance, address)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_with_one_being_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
def test_find_all_by_one_attribute_with_options
topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
assert_equal topics(:first), topics.last
@@ -137,14 +111,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert_equal 17, sig38.firm_id
end
- def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
- assert created_customer.persisted?
- end
-
def test_find_or_create_from_one_attribute_and_hash
number_of_companies = Company.count
sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
@@ -167,38 +133,12 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert_equal 23, sig38.client_of
end
- def test_find_or_create_from_one_aggregate_attribute
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance(Money.new(123))
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_aggregate_attribute_and_hash
- number_of_customers = Customer.count
- balance = Money.new(123)
- name = "Elizabeth"
- created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert created_customer.persisted?
- assert_equal balance, created_customer.balance
- assert_equal name, created_customer.name
- end
-
def test_find_or_initialize_from_one_attribute
sig38 = Company.find_or_initialize_by_name("38signals")
assert_equal "38signals", sig38.name
assert !sig38.persisted?
end
- def test_find_or_initialize_from_one_aggregate_attribute
- new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
- assert_equal 123, new_customer.balance.amount
- assert !new_customer.persisted?
- end
-
def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
assert_equal "Fortune 1000", c.name
@@ -285,13 +225,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
end
- def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
- new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal 123, new_customer.balance.amount
- assert_equal "Elizabeth", new_customer.name
- assert !new_customer.persisted?
- end
-
def test_find_or_initialize_from_one_attribute_and_hash
sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
assert_equal "38signals", sig38.name
@@ -300,15 +233,6 @@ class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
assert !sig38.persisted?
end
- def test_find_or_initialize_from_one_aggregate_attribute_and_hash
- balance = Money.new(123)
- name = "Elizabeth"
- new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
- assert_equal balance, new_customer.balance
- assert_equal name, new_customer.name
- assert !new_customer.persisted?
- end
-
def test_find_last_by_one_attribute
assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
assert_nil Topic.find_last_by_title("A title with no matches")
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 2650040a80..97ffc068af 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -79,6 +79,17 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_setting_time_attributes_with_time_zone_field_to_itself_should_not_be_marked_as_a_change
+ in_time_zone 'Paris' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'pirates'
+
+ pirate = target.create
+ pirate.created_on = pirate.created_on
+ assert !pirate.created_on_changed?
+ end
+ end
+
def test_time_attributes_changes_without_time_zone_by_skip
in_time_zone 'Paris' do
target = Class.new(ActiveRecord::Base)
@@ -485,16 +496,6 @@ class DirtyTest < ActiveRecord::TestCase
assert_not_nil pirate.previous_changes['updated_on'][1]
assert !pirate.previous_changes.key?('parrot_id')
assert !pirate.previous_changes.key?('created_on')
-
- pirate = Pirate.find_by_catchphrase("Ahoy!")
- pirate.update_attribute(:catchphrase, "Ninjas suck!")
-
- assert_equal 2, pirate.previous_changes.size
- assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase']
- assert_not_nil pirate.previous_changes['updated_on'][0]
- assert_not_nil pirate.previous_changes['updated_on'][1]
- assert !pirate.previous_changes.key?('parrot_id')
- assert !pirate.previous_changes.key?('created_on')
end
if ActiveRecord::Base.connection.supports_migrations?
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index e118add44c..91e1df91cd 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -6,14 +6,14 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_if_available_queries_for_explain_is_nil
with_queries(nil) do
- SUBSCRIBER.call
+ SUBSCRIBER.finish(nil, nil, {})
assert_nil Thread.current[:available_queries_for_explain]
end
end
def test_collects_nothing_if_the_payload_has_an_exception
with_queries([]) do |queries|
- SUBSCRIBER.call(:exception => Exception.new)
+ SUBSCRIBER.finish(nil, nil, :exception => Exception.new)
assert queries.empty?
end
end
@@ -21,7 +21,7 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_for_ignored_payloads
with_queries([]) do |queries|
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
- SUBSCRIBER.call(:name => ip)
+ SUBSCRIBER.finish(nil, nil, :name => ip)
end
assert queries.empty?
end
@@ -31,7 +31,7 @@ if ActiveRecord::Base.connection.supports_explain?
sql = 'select 1 from users'
binds = [1, 2]
with_queries([]) do |queries|
- SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds)
+ SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds)
assert_equal 1, queries.size
assert_equal sql, queries[0][0]
assert_equal binds, queries[0][1]
@@ -45,4 +45,4 @@ if ActiveRecord::Base.connection.supports_explain?
Thread.current[:available_queries_for_explain] = nil
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 810c1500cc..eedbaac3bd 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -80,7 +80,6 @@ class FinderRespondToTest < ActiveRecord::TestCase
private
def ensure_topic_method_is_not_cached(method_id)
- class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.any? { |m| m.to_s == method_id.to_s }
+ class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.include? method_id
end
-
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index f7ecab28ce..df21585961 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -45,6 +45,12 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(NoMethodError) { Topic.exists?([1,2]) }
end
+ def test_exists_does_not_select_columns_without_alias
+ assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
+ Topic.exists?
+ end
+ end
+
def test_exists_returns_true_with_one_record_and_no_args
assert Topic.exists?
end
@@ -63,24 +69,14 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.order(:id).uniq.exists?
end
- def test_does_not_exist_with_empty_table_and_no_args_given
- Topic.delete_all
- assert !Topic.exists?
+ def test_exists_with_includes_limit_and_empty_result
+ assert !Topic.includes(:replies).limit(0).exists?
+ assert !Topic.includes(:replies).limit(1).where('0 = 1').exists?
end
- def test_exists_with_aggregate_having_three_mappings
- existing_address = customers(:david).address
- assert Customer.exists?(:address => existing_address)
- end
-
- def test_exists_with_aggregate_having_three_mappings_with_one_difference
- existing_address = customers(:david).address
- assert !Customer.exists?(:address =>
- Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
- assert !Customer.exists?(:address =>
- Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
- assert !Customer.exists?(:address =>
- Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
+ def test_exists_with_empty_table_and_no_args_given
+ Topic.delete_all
+ assert !Topic.exists?
end
def test_exists_does_not_instantiate_records
@@ -301,14 +297,6 @@ class FinderTest < ActiveRecord::TestCase
assert_equal companies(:rails_core), firms.first
end
- def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
- david = customers(:david)
- assert Customer.scoped(:where => { 'customers.name' => david.name, :address => david.address }).find(david.id)
- assert_raise(ActiveRecord::RecordNotFound) {
- Customer.scoped(:where => { 'customers.name' => david.name + "1", :address => david.address }).find(david.id)
- }
- end
-
def test_find_on_association_proxy_conditions
assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort
end
@@ -383,48 +371,6 @@ class FinderTest < ActiveRecord::TestCase
assert_nil topic.last_read
end
- def test_hash_condition_find_with_aggregate_having_one_mapping
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customer = Customer.scoped(:where => {:balance => balance}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
- gps_location = customers(:david).gps_location
- assert_kind_of GpsLocation, gps_location
- found_customer = Customer.scoped(:where => {:gps_location => gps_location}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customer = Customer.scoped(:where => {:balance => balance.amount}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value
- gps_location = customers(:david).gps_location
- assert_kind_of GpsLocation, gps_location
- found_customer = Customer.scoped(:where => {:gps_location => gps_location.gps_location}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_aggregate_having_three_mappings
- address = customers(:david).address
- assert_kind_of Address, address
- found_customer = Customer.scoped(:where => {:address => address}).first
- assert_equal customers(:david), found_customer
- end
-
- def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not
- address = customers(:david).address
- assert_kind_of Address, address
- found_customer = Customer.scoped(:where => {:address => address, :name => customers(:david).name}).first
- assert_equal customers(:david), found_customer
- end
-
def test_condition_utc_time_interpolation_with_default_timezone_local
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
@@ -593,43 +539,9 @@ class FinderTest < ActiveRecord::TestCase
assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
end
- def test_find_by_one_attribute_that_is_an_aggregate
- address = customers(:david).address
- assert_kind_of Address, address
- found_customer = Customer.find_by_address(address)
- assert_equal customers(:david), found_customer
- end
-
- def test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference
- address = customers(:david).address
- assert_kind_of Address, address
- missing_address = Address.new(address.street, address.city, address.country + "1")
- assert_nil Customer.find_by_address(missing_address)
- missing_address = Address.new(address.street, address.city + "1", address.country)
- assert_nil Customer.find_by_address(missing_address)
- missing_address = Address.new(address.street + "1", address.city, address.country)
- assert_nil Customer.find_by_address(missing_address)
- end
-
- def test_find_by_two_attributes_that_are_both_aggregates
- balance = customers(:david).balance
- address = customers(:david).address
- assert_kind_of Money, balance
- assert_kind_of Address, address
- found_customer = Customer.find_by_balance_and_address(balance, address)
- assert_equal customers(:david), found_customer
- end
-
- def test_find_by_two_attributes_with_one_being_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customer = Customer.find_by_balance_and_name(balance, customers(:david).name)
- assert_equal customers(:david), found_customer
- end
-
def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
# ensure this test can run independently of order
- class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
+ class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit)
a = Account.where('firm_id = ?', 6).find_by_credit_limit(50)
assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 37fa13f771..afff020561 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -121,3 +121,19 @@ class << Time
@now = nil
end
end
+
+module LogIntercepter
+ attr_accessor :logged, :intercepted
+ def self.extended(base)
+ base.logged = []
+ end
+ def log(sql, name, binds = [], &block)
+ if @intercepted
+ @logged << [sql, name, binds]
+ yield
+ else
+ super(sql, name,binds, &block)
+ end
+ end
+end
+
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index 2f98d3c646..73a01906b9 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -95,7 +95,11 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
end
def test_mass_assigning_does_not_choke_on_nil
- Firm.new.assign_attributes(nil)
+ assert_nil Firm.new.assign_attributes(nil)
+ end
+
+ def test_mass_assigning_does_not_choke_on_empty_hash
+ assert_nil Firm.new.assign_attributes({})
end
def test_assign_attributes_uses_default_role_when_no_role_is_provided
@@ -247,6 +251,65 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
assert !Task.new.respond_to?("#{method}=")
end
end
+
+ test "ActiveRecord::Model.whitelist_attributes works for models which include Model" do
+ begin
+ prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true
+
+ klass = Class.new { include ActiveRecord::Model }
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
+ ensure
+ ActiveRecord::Model.whitelist_attributes = prev
+ end
+ end
+
+ test "ActiveRecord::Model.whitelist_attributes works for models which inherit Base" do
+ begin
+ prev, ActiveRecord::Model.whitelist_attributes = ActiveRecord::Model.whitelist_attributes, true
+
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
+
+ klass.attr_accessible 'foo'
+ assert_equal ['foo'], Class.new(klass).active_authorizers[:default].to_a
+ ensure
+ ActiveRecord::Model.whitelist_attributes = prev
+ end
+ end
+
+ test "ActiveRecord::Model.mass_assignment_sanitizer works for models which include Model" do
+ begin
+ sanitizer = Object.new
+ prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer
+
+ klass = Class.new { include ActiveRecord::Model }
+ assert_equal sanitizer, klass._mass_assignment_sanitizer
+
+ ActiveRecord::Model.mass_assignment_sanitizer = nil
+ klass = Class.new { include ActiveRecord::Model }
+ assert_not_nil klass._mass_assignment_sanitizer
+ ensure
+ ActiveRecord::Model.mass_assignment_sanitizer = prev
+ end
+ end
+
+ test "ActiveRecord::Model.mass_assignment_sanitizer works for models which inherit Base" do
+ begin
+ sanitizer = Object.new
+ prev, ActiveRecord::Model.mass_assignment_sanitizer = ActiveRecord::Model.mass_assignment_sanitizer, sanitizer
+
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal sanitizer, klass._mass_assignment_sanitizer
+
+ sanitizer2 = Object.new
+ klass.mass_assignment_sanitizer = sanitizer2
+ assert_equal sanitizer2, Class.new(klass)._mass_assignment_sanitizer
+ ensure
+ ActiveRecord::Model.mass_assignment_sanitizer = prev
+ end
+ end
end
@@ -878,4 +941,26 @@ class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase
assert_all_attributes(person.best_friends.first)
end
+ def test_mass_assignment_options_are_reset_after_exception
+ person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin)
+ person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin)
+
+ attributes = { :best_friend_attributes => { :comments => 'rides a sweet bike' } }
+ assert_raises(RuntimeError) { person.assign_attributes(attributes, :as => :admin) }
+ assert_equal 'm', person.best_friend.gender
+
+ person.best_friend_attributes = { :gender => 'f' }
+ assert_equal 'm', person.best_friend.gender
+ end
+
+ def test_mass_assignment_options_are_nested_correctly
+ person = NestedPerson.create!({ :first_name => 'David', :gender => 'm' }, :as => :admin)
+ person.create_best_friend!({ :first_name => 'Jeremy', :gender => 'm' }, :as => :admin)
+
+ attributes = { :best_friend_first_name => 'Josh', :best_friend_attributes => { :gender => 'f' } }
+ person.assign_attributes(attributes, :as => :admin)
+ assert_equal 'Josh', person.best_friend.first_name
+ assert_equal 'f', person.best_friend.gender
+ end
+
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index 18f8d82bfe..3014bbe273 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -174,7 +174,7 @@ module ActiveRecord
assert_not_equal "Z", bob.moment_of_truth.zone
# US/Eastern is -5 hours from GMT
assert_equal Rational(-5, 24), bob.moment_of_truth.offset
- assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM
+ assert_match(/\A-05:00\Z/, bob.moment_of_truth.zone)
assert_equal DateTime::ITALY, bob.moment_of_truth.start
end
end
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
index 8ab1c59724..264a99f9ce 100644
--- a/activerecord/test/cases/migration/references_index_test.rb
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -51,6 +51,8 @@ module ActiveRecord
end
def test_creates_polymorphic_index
+ return skip "Oracle Adapter does not support foreign keys if :polymorphic => true is used" if current_adapter? :OracleAdapter
+
connection.create_table table_name do |t|
t.references :foo, :polymorphic => true, :index => true
end
@@ -86,6 +88,7 @@ module ActiveRecord
end
def test_creates_polymorphic_index_for_existing_table
+ return skip "Oracle Adapter does not support foreign keys if :polymorphic => true is used" if current_adapter? :OracleAdapter
connection.create_table table_name
connection.change_table table_name do |t|
t.references :foo, :polymorphic => true, :index => true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 5d1bad0d54..3c0d2b18d9 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -56,6 +56,21 @@ class MigrationTest < ActiveRecord::TestCase
Person.reset_column_information
end
+ def test_migrator_versions
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal 3, ActiveRecord::Migrator.current_version
+ assert_equal 3, ActiveRecord::Migrator.last_version
+ assert_equal false, ActiveRecord::Migrator.needs_migration?
+
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
+ assert_equal 0, ActiveRecord::Migrator.current_version
+ assert_equal 3, ActiveRecord::Migrator.last_version
+ assert_equal true, ActiveRecord::Migrator.needs_migration?
+ end
+
def test_create_table_with_force_true_does_not_drop_nonexisting_table
if Person.connection.table_exists?(:testings2)
Person.connection.drop_table :testings2
@@ -523,7 +538,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
# One query for columns (delete_me table)
# One query for primary key (delete_me table)
# One query to do the bulk change
- assert_queries(3) do
+ assert_queries(3, :ignore_none => true) do
with_bulk_change_table do |t|
t.change :name, :string, :default => 'NONAME'
t.change :birthdate, :datetime
diff --git a/activerecord/test/cases/mysql_rake_test.rb b/activerecord/test/cases/mysql_rake_test.rb
new file mode 100644
index 0000000000..42a11b0171
--- /dev/null
+++ b/activerecord/test/cases/mysql_rake_test.rb
@@ -0,0 +1,246 @@
+require 'cases/helper'
+require 'mysql'
+
+module ActiveRecord
+ class MysqlDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_without_database
+ ActiveRecord::Base.expects(:establish_connection).
+ with('adapter' => 'mysql', 'database' => nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_default_options
+ @connection.expects(:create_database).
+ with('my-app-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_given_options
+ @connection.expects(:create_database).
+ with('my-app-db', {:charset => 'latin', :collation => 'latin_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge(
+ 'charset' => 'latin', 'collation' => 'latin_ci'
+ )
+ end
+
+ def test_establishes_connection_to_database
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true, :execute => true)
+ @error = Mysql::Error.new "Invalid permissions"
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'wossname'
+ }
+
+ $stdin.stubs(:gets).returns("secret\n")
+ $stdout.stubs(:print).returns(nil)
+ @error.stubs(:errno).returns(1045)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).raises(@error).then.
+ returns(true)
+ end
+
+ def test_root_password_is_requested
+ $stdin.expects(:gets).returns("secret\n")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_connection_established_as_root
+ ActiveRecord::Base.expects(:establish_connection).with({
+ 'adapter' => 'mysql',
+ 'database' => nil,
+ 'username' => 'root',
+ 'password' => 'secret'
+ })
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_database_created_by_root
+ @connection.expects(:create_database).
+ with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci')
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_grant_privileges_for_normal_user
+ @connection.expects(:execute).with("GRANT ALL PRIVILEGES ON my-app-db.* TO 'pat'@'localhost' IDENTIFIED BY 'wossname' WITH GRANT OPTION;")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_connection_established_as_normal_user
+ ActiveRecord::Base.expects(:establish_connection).returns do
+ ActiveRecord::Base.expects(:establish_connection).with({
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db',
+ 'username' => 'pat',
+ 'password' => 'secret'
+ })
+
+ raise @error
+ end
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_sends_output_to_stderr_when_other_errors
+ @error.stubs(:errno).returns(42)
+
+ $stderr.expects(:puts).at_least_once.returns(nil)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class MySQLDBDropTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:drop_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_mysql_database
+ ActiveRecord::Base.expects(:establish_connection).with @configuration
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
+
+ class MySQLPurgeTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:recreate_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'test-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_test_database
+ ActiveRecord::Base.expects(:establish_connection).with(:test)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_recreates_database_with_the_default_options
+ @connection.expects(:recreate_database).
+ with('test-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_recreates_database_with_the_given_options
+ @connection.expects(:recreate_database).
+ with('test-db', {:charset => 'latin', :collation => 'latin_ci'})
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
+ 'charset' => 'latin', 'collation' => 'latin_ci'
+ )
+ end
+ end
+
+ class MysqlDBCharsetTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_charset
+ @connection.expects(:charset)
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
+
+ class MySQLStructureDumpTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:structure_dump => true)
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'test-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_structure_dump
+ filename = "awesome-file.sql"
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+ @connection.expects(:structure_dump)
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm(filename)
+ end
+ end
+
+ class MySQLStructureLoadTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub
+ @configuration = {
+ 'adapter' => 'mysql',
+ 'database' => 'test-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_structure_load
+ filename = "awesome-file.sql"
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+ @connection.expects(:execute).twice
+
+ open(filename, 'w') { |f| f.puts("SELECT CURDATE();") }
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ ensure
+ FileUtils.rm(filename)
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index bf825c002a..c886160af3 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -19,7 +19,6 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_found_items_are_cached
- Topic.columns
all_posts = Topic.base
assert_queries(1) do
@@ -46,6 +45,15 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
end
+ def test_method_missing_priority_when_delegating
+ klazz = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ scope :since, Proc.new { where('written_on >= ?', Time.now - 1.day) }
+ scope :to, Proc.new { where('written_on <= ?', Time.now) }
+ end
+ assert_equal klazz.to.since.all, klazz.since.to.all
+ end
+
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
assert Topic.approved.respond_to?(:limit)
assert Topic.approved.respond_to?(:count)
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 0559bbbe9a..3daa033ed0 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -781,6 +781,16 @@ module NestedAttributesOnACollectionAssociationTests
assert_nothing_raised(NoMethodError) { @pirate.save! }
end
+ def test_numeric_colum_changes_from_zero_to_no_empty_string
+ Man.accepts_nested_attributes_for(:interests)
+ Interest.validates_numericality_of(:zine_id)
+ man = Man.create(:name => 'John')
+ interest = man.interests.create(:topic=>'bar',:zine_id => 0)
+ assert interest.save
+
+ assert !man.update_attributes({:interests_attributes => { :id => interest.id, :zine_id => 'foo' }})
+ end
+
private
def association_setter
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 0933a4ff3d..b7b77b24af 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -305,6 +305,13 @@ class PersistencesTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
end
+ def test_destroy!
+ topic = Topic.find(1)
+ assert_equal topic, topic.destroy!, 'topic.destroy! did not return self'
+ assert topic.frozen?, 'topic not frozen after destroy!'
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
+ end
+
def test_record_not_found_exception
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(99999) }
end
@@ -364,71 +371,10 @@ class PersistencesTest < ActiveRecord::TestCase
assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
end
- def test_update_attribute
- assert !Topic.find(1).approved?
- Topic.find(1).update_attribute("approved", true)
- assert Topic.find(1).approved?
-
- Topic.find(1).update_attribute(:approved, false)
- assert !Topic.find(1).approved?
- end
-
def test_update_attribute_does_not_choke_on_nil
assert Topic.find(1).update_attributes(nil)
end
- def test_update_attribute_for_readonly_attribute
- minivan = Minivan.find('m1')
- assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
- end
-
- # This test is correct, but it is hard to fix it since
- # update_attribute trigger simply call save! that triggers
- # all callbacks.
- # def test_update_attribute_with_one_changed_and_one_updated
- # t = Topic.order('id').limit(1).first
- # title, author_name = t.title, t.author_name
- # t.author_name = 'John'
- # t.update_attribute(:title, 'super_title')
- # assert_equal 'John', t.author_name
- # assert_equal 'super_title', t.title
- # assert t.changed?, "topic should have changed"
- # assert t.author_name_changed?, "author_name should have changed"
- # assert !t.title_changed?, "title should not have changed"
- # assert_nil t.title_change, 'title change should be nil'
- # assert_equal ['author_name'], t.changed
- #
- # t.reload
- # assert_equal 'David', t.author_name
- # assert_equal 'super_title', t.title
- # end
-
- def test_update_attribute_with_one_updated
- t = Topic.first
- t.update_attribute(:title, 'super_title')
- assert_equal 'super_title', t.title
- assert !t.changed?, "topic should not have changed"
- assert !t.title_changed?, "title should not have changed"
- assert_nil t.title_change, 'title change should be nil'
-
- t.reload
- assert_equal 'super_title', t.title
- end
-
- def test_update_attribute_for_updated_at_on
- developer = Developer.find(1)
- prev_month = Time.now.prev_month
-
- developer.update_attribute(:updated_at, prev_month)
- assert_equal prev_month, developer.updated_at
-
- developer.update_attribute(:salary, 80001)
- assert_not_equal prev_month, developer.updated_at
-
- developer.reload
- assert_not_equal prev_month, developer.updated_at
- end
-
def test_update_column
topic = Topic.find(1)
topic.update_column("approved", true)
@@ -460,7 +406,7 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_column_should_not_leave_the_object_dirty
topic = Topic.find(1)
- topic.update_attribute("content", "Have a nice day")
+ topic.update_column("content", "Have a nice day")
topic.reload
topic.update_column(:content, "You too")
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index fba3006ebe..0a6354f5cc 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -17,7 +17,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
end
def checkout_connections
- ActiveRecord::Model.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3}))
+ ActiveRecord::Model.establish_connection(@connection.merge({:pool => 2, :checkout_timeout => 0.3}))
@connections = []
@timed_out = 0
@@ -34,7 +34,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
# Will deadlock due to lack of Monitor timeouts in 1.9
def checkout_checkin_connections(pool_size, threads)
- ActiveRecord::Model.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5}))
+ ActiveRecord::Model.establish_connection(@connection.merge({:pool => pool_size, :checkout_timeout => 0.5}))
@connection_count = 0
@timed_out = 0
threads.times do
diff --git a/activerecord/test/cases/postgresql_rake_test.rb b/activerecord/test/cases/postgresql_rake_test.rb
new file mode 100644
index 0000000000..e8769bd4df
--- /dev/null
+++ b/activerecord/test/cases/postgresql_rake_test.rb
@@ -0,0 +1,200 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class PostgreSQLDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_default_encoding
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'utf8'))
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_creates_database_with_given_encoding
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'latin'))
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration.
+ merge('encoding' => 'latin')
+ end
+
+ def test_establishes_connection_to_new_database
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+
+ def test_db_create_with_error_prints_message
+ ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
+
+ $stderr.stubs(:puts).returns(true)
+ $stderr.expects(:puts).
+ with("Couldn't create database for #{@configuration.inspect}")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ end
+
+ class PostgreSQLDBDropTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:drop_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ end
+
+ class PostgreSQLPurgeTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true, :drop_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:clear_active_connections!).returns(true)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_clears_active_connections
+ ActiveRecord::Base.expects(:clear_active_connections!)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_establishes_connection_to_postgresql_database
+ ActiveRecord::Base.expects(:establish_connection).with(
+ 'adapter' => 'postgresql',
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_drops_database
+ @connection.expects(:drop_database).with('my-app-db')
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_creates_database
+ @connection.expects(:create_database).
+ with('my-app-db', @configuration.merge('encoding' => 'utf8'))
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+
+ def test_establishes_connection
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.purge @configuration
+ end
+ end
+
+ class PostgreSQLDBCharsetTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:create_database => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_charset
+ @connection.expects(:encoding)
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ end
+
+ class PostgreSQLStructureDumpTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub(:structure_dump => true)
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ Kernel.stubs(:system)
+ end
+
+ def test_structure_dump
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("pg_dump -i -s -x -O -f #{filename} my-app-db").returns(true)
+ @connection.expects(:schema_search_path).returns("foo")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm(filename)
+ end
+ end
+
+ class PostgreSQLStructureLoadTest < ActiveRecord::TestCase
+ def setup
+ @connection = stub
+ @configuration = {
+ 'adapter' => 'postgresql',
+ 'database' => 'my-app-db'
+ }
+
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ Kernel.stubs(:system)
+ end
+
+ def test_structure_dump
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("psql -f #{filename} my-app-db")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index a712e5f689..08f655d7fa 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -151,16 +151,11 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_cache_does_not_wrap_string_results_in_arrays
- if current_adapter?(:SQLite3Adapter)
- require 'sqlite3/version'
- sqlite3_version = RUBY_PLATFORM =~ /java/ ? Jdbc::SQLite3::VERSION : SQLite3::VERSION
- end
-
Task.cache do
# Oracle adapter returns count() as Fixnum or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter) && sqlite3_version > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:SQLite3Adapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter)
# Future versions of the sqlite3 adapter will return numeric
assert_instance_of Fixnum,
Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index 576ab60090..e53a27d5dd 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -64,7 +64,7 @@ module ActiveRecord
spec.config[:reaping_frequency] = 0.0001
pool = ConnectionPool.new spec
- pool.timeout = 0
+ pool.dead_connection_timeout = 0
conn = pool.checkout
count = pool.connections.length
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 7dd5698dcf..bac3376c9f 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -83,30 +83,6 @@ class ReflectionTest < ActiveRecord::TestCase
end
end
- def test_aggregation_reflection
- reflection_for_address = AggregateReflection.new(
- :composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
- )
-
- reflection_for_balance = AggregateReflection.new(
- :composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
- )
-
- reflection_for_gps_location = AggregateReflection.new(
- :composed_of, :gps_location, { }, Customer
- )
-
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
- assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)
-
- assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
-
- assert_equal Address, Customer.reflect_on_aggregation(:address).klass
-
- assert_equal Money, Customer.reflect_on_aggregation(:balance).klass
- end
-
def test_reflect_on_all_autosave_associations
expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] }
received = Pirate.reflect_on_all_autosave_associations
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
new file mode 100644
index 0000000000..90c690e266
--- /dev/null
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -0,0 +1,19 @@
+require "cases/helper"
+require 'models/post'
+
+module ActiveRecord
+ class WhereTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_where_error
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Post.where(:id => { 'posts.author_id' => 10 }).first
+ end
+ end
+
+ def test_where_with_table_name
+ post = Post.first
+ assert_equal post, Post.where(:posts => { 'id' => post.id }).first
+ end
+ end
+end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 2dc8f0053b..6c5bee7382 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -226,7 +226,6 @@ class RelationTest < ActiveRecord::TestCase
assert_no_queries do
assert_equal [], Developer.none
assert_equal [], Developer.scoped.none
- assert Developer.none.is_a?(ActiveRecord::NullRelation)
end
end
@@ -236,6 +235,12 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ def test_none_chainable_to_existing_scope_extension_method
+ assert_no_queries do
+ assert_equal 1, Topic.anonymous_extension.none.one
+ end
+ end
+
def test_none_chained_to_methods_firing_queries_straight_to_db
assert_no_queries do
assert_equal [], Developer.none.pluck(:id) # => uses select_all
@@ -690,6 +695,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 1, comments.count
end
+ def test_relation_merging_with_association
+ assert_queries(2) do # one for loading post, and another one merged query
+ post = Post.where(:body => 'Such a lovely day').first
+ comments = Comment.where(:body => 'Thank you for the welcome').merge(post.comments)
+ assert_equal 1, comments.count
+ end
+ end
+
def test_count
posts = Post.scoped
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index ab80dd1d6d..01dd25a9df 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -257,6 +257,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dump_includes_uuid_shorthand_definition
+ output = standard_dump
+ if %r{create_table "poistgresql_uuids"} =~ output
+ assert_match %r{t.uuid "guid"}, output
+ end
+ end
+
def test_schema_dump_includes_hstores_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_hstores"} =~ output
@@ -294,4 +301,36 @@ class SchemaDumperTest < ActiveRecord::TestCase
output = standard_dump
assert_match %r{create_table "subscribers", :id => false}, output
end
+
+ class CreateDogMigration < ActiveRecord::Migration
+ def up
+ create_table("dogs") do |t|
+ t.column :name, :string
+ end
+ add_index "dogs", [:name]
+ end
+ def down
+ drop_table("dogs")
+ end
+ end
+
+ def test_schema_dump_with_table_name_prefix_and_suffix
+ original, $stdout = $stdout, StringIO.new
+ ActiveRecord::Base.table_name_prefix = 'foo_'
+ ActiveRecord::Base.table_name_suffix = '_bar'
+
+ migration = CreateDogMigration.new
+ migration.migrate(:up)
+
+ output = standard_dump
+ assert_no_match %r{create_table "foo_.+_bar"}, output
+ assert_no_match %r{create_index "foo_.+_bar"}, output
+ assert_no_match %r{create_table "schema_migrations"}, output
+ ensure
+ migration.migrate(:down)
+
+ ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = ''
+ $stdout = original
+ end
+
end
diff --git a/activerecord/test/cases/sqlite_rake_test.rb b/activerecord/test/cases/sqlite_rake_test.rb
new file mode 100644
index 0000000000..b5557fc953
--- /dev/null
+++ b/activerecord/test/cases/sqlite_rake_test.rb
@@ -0,0 +1,170 @@
+require 'cases/helper'
+require 'pathname'
+
+module ActiveRecord
+ class SqliteDBCreateTest < ActiveRecord::TestCase
+ def setup
+ @database = 'db_create.sqlite3'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ File.stubs(:exist?).returns(false)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_checks_database_exists
+ File.expects(:exist?).with(@database).returns(false)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_when_file_exists
+ File.stubs(:exist?).returns(true)
+
+ $stderr.expects(:puts).with("#{@database} already exists")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_with_file_does_nothing
+ File.stubs(:exist?).returns(true)
+ $stderr.stubs(:puts).returns(nil)
+
+ ActiveRecord::Base.expects(:establish_connection).never
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_establishes_a_connection
+ ActiveRecord::Base.expects(:establish_connection).with(@configuration)
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+
+ def test_db_create_with_error_prints_message
+ ActiveRecord::Base.stubs(:establish_connection).raises(Exception)
+
+ $stderr.stubs(:puts).returns(true)
+ $stderr.expects(:puts).
+ with("Couldn't create database for #{@configuration.inspect}")
+
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+ end
+ end
+
+ class SqliteDBDropTest < ActiveRecord::TestCase
+ def setup
+ @database = "db_create.sqlite3"
+ @path = stub(:to_s => '/absolute/path', :absolute? => true)
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ Pathname.stubs(:new).returns(@path)
+ File.stubs(:join).returns('/former/relative/path')
+ FileUtils.stubs(:rm).returns(true)
+ end
+
+ def test_creates_path_from_database
+ Pathname.expects(:new).with(@database).returns(@path)
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_removes_file_with_absolute_path
+ File.stubs(:exist?).returns(true)
+ @path.stubs(:absolute?).returns(true)
+
+ FileUtils.expects(:rm).with('/absolute/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_generates_absolute_path_with_given_root
+ @path.stubs(:absolute?).returns(false)
+
+ File.expects(:join).with('/rails/root', @path).
+ returns('/former/relative/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+
+ def test_removes_file_with_relative_path
+ File.stubs(:exist?).returns(true)
+ @path.stubs(:absolute?).returns(false)
+
+ FileUtils.expects(:rm).with('/former/relative/path')
+
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+ end
+ end
+
+ class SqliteDBCharsetTest < ActiveRecord::TestCase
+ def setup
+ @database = 'db_create.sqlite3'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+
+ File.stubs(:exist?).returns(false)
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+ end
+
+ def test_db_retrieves_charset
+ @connection.expects(:encoding)
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration, '/rails/root'
+ end
+ end
+
+ class SqliteStructureDumpTest < ActiveRecord::TestCase
+ def setup
+ @database = "db_create.sqlite3"
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+ end
+
+ def test_structure_dump
+ dbfile = @database
+ filename = "awesome-file.sql"
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump @configuration, filename, '/rails/root'
+ assert File.exists?(dbfile)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm(filename)
+ FileUtils.rm(dbfile)
+ end
+ end
+
+ class SqliteStructureLoadTest < ActiveRecord::TestCase
+ def setup
+ @database = "db_create.sqlite3"
+ @configuration = {
+ 'adapter' => 'sqlite3',
+ 'database' => @database
+ }
+ end
+
+ def test_structure_load
+ dbfile = @database
+ filename = "awesome-file.sql"
+
+ open(filename, 'w') { |f| f.puts("select datetime('now', 'localtime');") }
+ ActiveRecord::Tasks::DatabaseTasks.structure_load @configuration, filename, '/rails/root'
+ assert File.exists?(dbfile)
+ ensure
+ FileUtils.rm(filename)
+ FileUtils.rm(dbfile)
+ end
+ end
+end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index e1d0f1f799..e401dd4b12 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -3,15 +3,17 @@ require 'models/admin'
require 'models/admin/user'
class StoreTest < ActiveRecord::TestCase
+ fixtures :'admin/users'
+
setup do
- @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => true)
+ @john = Admin::User.create!(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => true)
end
test "reading store attributes through accessors" do
assert_equal 'black', @john.color
assert_nil @john.homepage
end
-
+
test "writing store attributes through accessors" do
@john.color = 'red'
@john.homepage = '37signals.com'
@@ -41,6 +43,41 @@ class StoreTest < ActiveRecord::TestCase
assert_equal false, @john.remember_login
end
+ test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do
+ @john.json_data = HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy')
+ @john.height = 'low'
+ assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
+ assert_equal 'low', @john.json_data[:height]
+ assert_equal 'low', @john.json_data['height']
+ assert_equal 'heavy', @john.json_data[:weight]
+ assert_equal 'heavy', @john.json_data['weight']
+ end
+
+ test "convert store attributes from Hash to HashWithIndifferentAccess saving the data and access attributes indifferently" do
+ user = Admin::User.find_by_name('Jamis')
+ assert_equal 'symbol', user.settings[:symbol]
+ assert_equal 'symbol', user.settings['symbol']
+ assert_equal 'string', user.settings[:string]
+ assert_equal 'string', user.settings['string']
+ assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess)
+
+ user.height = 'low'
+ assert_equal 'symbol', user.settings[:symbol]
+ assert_equal 'symbol', user.settings['symbol']
+ assert_equal 'string', user.settings[:string]
+ assert_equal 'string', user.settings['string']
+ assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess)
+ end
+
+ test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do
+ @john.json_data = "somedata"
+ @john.height = 'low'
+ assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess)
+ assert_equal 'low', @john.json_data[:height]
+ assert_equal 'low', @john.json_data['height']
+ assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any?
+ end
+
test "reading store attributes through accessors encoded with JSON" do
assert_equal 'tall', @john.height
assert_nil @john.weight
@@ -74,4 +111,8 @@ class StoreTest < ActiveRecord::TestCase
@john.is_a_good_guy = false
assert_equal false, @john.is_a_good_guy
end
+
+ test "stored attributes are returned" do
+ assert_equal [:color, :homepage], Admin::User.stored_attributes[:settings]
+ end
end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 447aa29ffe..7df6993b30 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -11,7 +11,7 @@ class TimestampTest < ActiveRecord::TestCase
def setup
@developer = Developer.first
- @developer.update_attribute(:updated_at, Time.now.prev_month)
+ @developer.update_column(:updated_at, Time.now.prev_month)
@previously_updated_at = @developer.updated_at
end
@@ -133,7 +133,7 @@ class TimestampTest < ActiveRecord::TestCase
pet = Pet.first
owner = pet.owner
- owner.update_attribute(:happy_at, 3.days.ago)
+ owner.update_column(:happy_at, 3.days.ago)
previously_owner_updated_at = owner.updated_at
pet.name = "I'm a parrot"
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 203dd054f1..a9ccd00fac 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -362,6 +362,16 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_rollback_when_saving_a_frozen_record
+ topic = Topic.new(:title => 'test')
+ topic.freeze
+ e = assert_raise(RuntimeError) { topic.save }
+ assert_equal "can't modify frozen Hash", e.message
+ assert !topic.persisted?, 'not persisted'
+ assert_nil topic.id
+ assert topic.frozen?, 'not frozen'
+ end
+
def test_restore_active_record_state_for_all_records_in_a_transaction
topic_1 = Topic.new(:title => 'test_1')
topic_2 = Topic.new(:title => 'test_2')
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 88751a72f9..12373333b0 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -92,7 +92,7 @@ class DefaultXmlSerializationTest < ActiveRecord::TestCase
end
def test_should_serialize_datetime
- assert_match %r{<created-at type=\"datetime\">2006-08-01T00:00:00Z</created-at>}, @xml
+ assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @xml
end
def test_should_serialize_boolean
@@ -109,7 +109,7 @@ class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase
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
+ assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
ensure
Time.zone = timezone
end
@@ -118,7 +118,7 @@ class DefaultXmlSerializationTimezoneTest < ActiveRecord::TestCase
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
+ assert_match %r{<updated-at type=\"dateTime\">2006-07-31T17:00:00-07:00</updated-at>}, toy.to_xml
ensure
Time.zone = timezone
end
@@ -152,7 +152,7 @@ class NilXmlSerializationTest < ActiveRecord::TestCase
assert %r{<created-at (.*)></created-at>}.match(@xml)
attributes = $1
assert_match %r{nil="true"}, attributes
- assert_match %r{type="datetime"}, attributes
+ assert_match %r{type="dateTime"}, attributes
end
def test_should_serialize_boolean
@@ -188,7 +188,7 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
- assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
+ assert_equal "dateTime" , xml.elements["//written-on"].attributes['type']
assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
@@ -198,7 +198,7 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
if current_adapter?(:SybaseAdapter)
assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
- assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
+ assert_equal "dateTime" , xml.elements["//last-read"].attributes['type']
else
# Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
assert_equal "2004-04-15", xml.elements["//last-read"].text
@@ -211,7 +211,7 @@ class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
assert_equal "boolean" , xml.elements["//approved"].attributes['type']
assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
- assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
+ assert_equal "dateTime" , xml.elements["//bonus-time"].attributes['type']
end
end
diff --git a/activerecord/test/fixtures/admin/users.yml b/activerecord/test/fixtures/admin/users.yml
index 6f11f2509e..e2884beda5 100644
--- a/activerecord/test/fixtures/admin/users.yml
+++ b/activerecord/test/fixtures/admin/users.yml
@@ -5,3 +5,6 @@ david:
jamis:
name: Jamis
account: signals37
+ settings:
+ :symbol: symbol
+ string: string
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 807f25b687..594c484f20 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -1,13 +1,5 @@
class Customer < ActiveRecord::Base
-
cattr_accessor :gps_conversion_was_run
-
- composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true
- composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
- composed_of :gps_location, :allow_nil => true
- composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location),
- :converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)}
- composed_of :fullname, :mapping => %w(name to_s), :constructor => Proc.new { |name| Fullname.parse(name) }, :converter => :parse
end
class Address
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 83482f4d07..9fefa9ad3a 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -65,11 +65,6 @@ class AuditLog < ActiveRecord::Base
end
DeveloperSalary = Struct.new(:amount)
-class DeveloperWithAggregate < ActiveRecord::Base
- self.table_name = 'developers'
- composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)]
-end
-
class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
self.table_name = 'developers'
has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id'
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index d5c0b351aa..33cd6020a1 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -88,6 +88,25 @@ class TightDescendant < TightPerson; end
class RichPerson < ActiveRecord::Base
self.table_name = 'people'
-
+
has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
end
+
+class NestedPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ attr_accessible :first_name, :best_friend_first_name, :best_friend_attributes
+ attr_accessible :first_name, :gender, :comments, :as => :admin
+ attr_accessible :best_friend_attributes, :best_friend_first_name, :as => :admin
+
+ has_one :best_friend, :class_name => 'NestedPerson', :foreign_key => :best_friend_id
+ accepts_nested_attributes_for :best_friend, :update_only => true
+
+ def comments=(new_comments)
+ raise RuntimeError
+ end
+
+ def best_friend_first_name=(new_name)
+ assign_attributes({ :best_friend_attributes => { :first_name => new_name } })
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index e51db50ae3..5f01f1fc50 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,6 +1,6 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
+ %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids
postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -59,6 +59,14 @@ _SQL
_SQL
execute <<_SQL
+ CREATE TABLE postgresql_uuids (
+ id SERIAL PRIMARY KEY,
+ guid uuid,
+ compact_guid uuid
+ );
+_SQL
+
+ execute <<_SQL
CREATE TABLE postgresql_tsvectors (
id SERIAL PRIMARY KEY,
text_vector tsvector
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 79ea9c70ea..00fcfe2001 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,7 +1,37 @@
## Rails 4.0.0 (unreleased) ##
+* Add `Time#prev_quarter' and 'Time#next_quarter' short-hands for months_ago(3) and months_since(3). *SungHee Kang*
+
+* Remove obsolete and unused `require_association` method from dependencies. *fxn*
+
+* Add `:instance_accessor` option for `config_accessor`.
+
+ class User
+ include ActiveSupport::Configurable
+ config_accessor :allowed_access, instance_accessor: false
+ end
+
+ User.new.allowed_access = true # => NoMethodError
+ User.new.allowed_access # => NoMethodError
+
+ *Francesco Rodriguez*
+
+* ActionView::Helpers::NumberHelper methods have been moved to ActiveSupport::NumberHelper and are now available via
+ Numeric#to_s. Numeric#to_s now accepts the formatting options :phone, :currency, :percentage, :delimited,
+ :rounded, :human, and :human_size. *Andrew Mutz*
+
+* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys`, and `Hash#deep_transform_keys!`. *Mark McSpadden*
+
+* Changed xml type `datetime` to `dateTime` (with upper case letter `T`). *Angelo Capilleri*
+
+* Add `:instance_accessor` option for `class_attribute`. *Alexey Vakhov*
+
* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White*
+* Adds `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a +Hash+ instance into strings *Lucas Húngaro*
+
+* Adds `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a +Hash+ instance into symbols *Lucas Húngaro*
+
* `Object#try` can't call private methods. *Vasiliy Ermolovich*
* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez*
@@ -43,8 +73,16 @@
* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead
of wrapping them in strings for safety.
+* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge*
+
+
+## Rails 3.2.5 (Jun 1, 2012) ##
+
+* ActiveSupport::JSON::Variable is deprecated. Define your own #as_json and #encode_json methods
+ for custom JSON string literals. *Erich Menge*
+
-## Rails 3.2.4 (unreleased) ##
+## Rails 3.2.4 (May 31, 2012) ##
* Added #beginning_of_hour and #end_of_hour to Time and DateTime core
extensions. *Mark J. Titorenko*
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 2c874e932e..fa38d5c1e3 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.'
s.required_ruby_version = '>= 1.9.3'
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
@@ -20,5 +21,5 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('multi_json', '~> 1.3')
- s.add_dependency('tzinfo', '~> 0.3.31')
+ s.add_dependency('tzinfo', '~> 0.3.33')
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 8f018dcbc6..56d6676961 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -25,6 +25,7 @@ require 'securerandom'
require "active_support/dependencies/autoload"
require "active_support/version"
require "active_support/logger"
+require "active_support/lazy_load_hooks"
module ActiveSupport
extend ActiveSupport::Autoload
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 55791bfa56..a62214d604 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -405,7 +405,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
- # Increment an integer value in the cache.
+ # Decrement an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 89bdb741d0..5be63af342 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -81,7 +81,8 @@ module ActiveSupport
if File.exist?(file_name)
File.open(file_name) { |f| Marshal.load(f) }
end
- rescue
+ rescue => e
+ logger.error("FileStoreError (#{e}): #{e.message}") if logger
nil
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index a9253c186d..0aa3efbb63 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -328,17 +328,26 @@ module ActiveSupport
# if it was not yet defined.
# This generated method plays caching role.
def __define_callbacks(kind, object) #:nodoc:
- chain = object.send("_#{kind}_callbacks")
- name = "_run_callbacks_#{chain.object_id.abs}"
+ name = __callback_runner_name(kind)
unless object.respond_to?(name, true)
+ str = object.send("_#{kind}_callbacks").compile
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}() #{chain.compile} end
+ def #{name}() #{str} end
protected :#{name}
RUBY_EVAL
end
name
end
+ def __reset_runner(symbol)
+ name = __callback_runner_name(symbol)
+ undef_method(name) if method_defined?(name)
+ end
+
+ def __callback_runner_name(kind)
+ "_run__#{self.name.hash.abs}__#{kind}__callbacks"
+ end
+
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
#
@@ -350,6 +359,7 @@ module ActiveSupport
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
chain = target.send("_#{name}_callbacks")
yield target, chain.dup, type, filters, options
+ target.__reset_runner(name)
end
end
@@ -437,9 +447,12 @@ module ActiveSupport
chain = target.send("_#{symbol}_callbacks").dup
callbacks.each { |c| chain.delete(c) }
target.send("_#{symbol}_callbacks=", chain)
+ target.__reset_runner(symbol)
end
self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
+
+ __reset_runner(symbol)
end
# Define sets of events in the object lifecycle that support callbacks.
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index a8aa53a80f..4fb8c7af3f 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -37,29 +37,77 @@ module ActiveSupport
yield config
end
- # Allows you to add shortcut so that you don't have to refer to attribute through config.
- # Also look at the example for config to contrast.
+ # Allows you to add shortcut so that you don't have to refer to attribute
+ # through config. Also look at the example for config to contrast.
+ #
+ # Defines both class and instance config accessors.
#
# class User
# include ActiveSupport::Configurable
# config_accessor :allowed_access
# end
#
+ # User.allowed_access # => nil
+ # User.allowed_access = false
+ # User.allowed_access # => false
+ #
# user = User.new
+ # user.allowed_access # => false
# user.allowed_access = true
# user.allowed_access # => true
#
+ # User.allowed_access # => false
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # class User
+ # include ActiveSupport::Configurable
+ # config_accessor :"1_Badname"
+ # end
+ # # => NameError: invalid config attribute name
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # class User
+ # include ActiveSupport::Configurable
+ # config_accessor :allowed_access, instance_reader: false, instance_writer: false
+ # end
+ #
+ # User.allowed_access = false
+ #  User.allowed_access # => false
+ #
+ # User.new.allowed_access = true # => NoMethodError
+ # User.new.allowed_access # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class User
+ # include ActiveSupport::Configurable
+ # config_accessor :allowed_access, instance_accessor: false
+ # end
+ #
+ # User.allowed_access = false
+ #  User.allowed_access # => false
+ #
+ # User.new.allowed_access = true # => NoMethodError
+ # User.new.allowed_access # => NoMethodError
def config_accessor(*names)
options = names.extract_options!
names.each do |name|
+ raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/
+
reader, line = "def #{name}; config.#{name}; end", __LINE__
writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__
singleton_class.class_eval reader, __FILE__, line
singleton_class.class_eval writer, __FILE__, line
- class_eval reader, __FILE__, line unless options[:instance_reader] == false
- class_eval writer, __FILE__, line unless options[:instance_writer] == false
+
+ unless options[:instance_accessor] == false
+ class_eval reader, __FILE__, line unless options[:instance_reader] == false
+ class_eval writer, __FILE__, line unless options[:instance_writer] == false
+ end
end
end
end
@@ -79,7 +127,6 @@ module ActiveSupport
#
# user.config.allowed_access # => true
# user.config.level # => 1
- #
def config
@_config ||= self.class.config.inheritable_copy
end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 44d90ef732..a8f9dddae5 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -1,40 +1,48 @@
class Array
# Returns the tail of the array from +position+.
#
- # %w( a b c d ).from(0) # => %w( a b c d )
- # %w( a b c d ).from(2) # => %w( c d )
- # %w( a b c d ).from(10) # => %w()
- # %w().from(0) # => %w()
+ # %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
+ # %w( a b c d ).from(2) # => ["c", "d"]
+ # %w( a b c d ).from(10) # => []
+ # %w().from(0) # => []
def from(position)
self[position, length] || []
end
# Returns the beginning of the array up to +position+.
#
- # %w( a b c d ).to(0) # => %w( a )
- # %w( a b c d ).to(2) # => %w( a b c )
- # %w( a b c d ).to(10) # => %w( a b c d )
- # %w().to(0) # => %w()
+ # %w( a b c d ).to(0) # => ["a"]
+ # %w( a b c d ).to(2) # => ["a", "b", "c"]
+ # %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
+ # %w().to(0) # => []
def to(position)
first position + 1
end
# Equal to <tt>self[1]</tt>.
+ #
+ # %w( a b c d e).second # => "b"
def second
self[1]
end
# Equal to <tt>self[2]</tt>.
+ #
+ # %w( a b c d e).third # => "c"
def third
self[2]
end
# Equal to <tt>self[3]</tt>.
+ #
+ # %w( a b c d e).fourth # => "d"
def fourth
self[3]
end
# Equal to <tt>self[4]</tt>.
+ #
+ # %w( a b c d e).fifth # => "e"
def fifth
self[4]
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 24aa28b895..1e0de651c7 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -4,10 +4,55 @@ require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/core_ext/string/inflections'
class Array
- # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
- # * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
- # * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
- # * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
+ # Converts the array to a comma-separated sentence where the last element is
+ # joined by the connector word.
+ #
+ # You can pass the following options to change the default behaviour. If you
+ # pass an option key that doesn't exist in the list below, it will raise an
+ # <tt>ArgumentError</tt>.
+ #
+ # Options:
+ #
+ # * <tt>:words_connector</tt> - The sign or word used to join the elements
+ # in arrays with two or more elements (default: ", ").
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements
+ # in arrays with two elements (default: " and ").
+ # * <tt>:last_word_connector</tt> - The sign or word used to join the last element
+ # in arrays with three or more elements (default: ", and ").
+ # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
+ # the connector options defined on the 'support.array' namespace in the
+ # corresponding dictionary file.
+ #
+ # [].to_sentence # => ""
+ # ['one'].to_sentence # => "one"
+ # ['one', 'two'].to_sentence # => "one and two"
+ # ['one', 'two', 'three'].to_sentence # => "one, two, and three"
+ #
+ # ['one', 'two'].to_sentence(passing: 'invalid option')
+ # # => ArgumentError: Unknown key :passing
+ #
+ # ['one', 'two'].to_sentence(two_words_connector: '-')
+ # # => "one-two"
+ #
+ # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ')
+ # # => "one or two or at least three"
+ #
+ # Examples using <tt>:locale</tt> option:
+ #
+ # # Given this locale dictionary:
+ # # 
+ # # es:
+ # # support:
+ # # array:
+ # # words_connector: " o "
+ # # two_words_connector: " y "
+ # # last_word_connector: " o al menos "
+ #
+ # ['uno', 'dos'].to_sentence(locale: :es)
+ # # => "uno y dos"
+ #
+ # ['uno', 'dos', 'tres'].to_sentence(locale: :es)
+ # # => "uno o dos o al menos tres"
def to_sentence(options = {})
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
@@ -39,7 +84,17 @@ class Array
end
# Converts a collection of elements into a formatted string by calling
- # <tt>to_s</tt> on all elements and joining them:
+ # <tt>to_s</tt> on all elements and joining them. Having this model:
+ #
+ # class Blog < ActiveRecord::Base
+ # def to_s
+ # title
+ # end
+ # end
+ #
+ # Blog.all.map(&:title) #=> ["First Post", "Second Post", "Third post"]
+ #
+ # <tt>to_formatted_s</tt> shows us:
#
# Blog.all.to_formatted_s # => "First PostSecond PostThird Post"
#
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index ac1ae53db0..a184eb492a 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -2,18 +2,21 @@ class Array
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
#
- # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group}
# ["1", "2", "3"]
# ["4", "5", "6"]
- # ["7", nil, nil]
+ # ["7", "8", "9"]
+ # ["10", nil, nil]
#
- # %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
+ # %w(1 2 3 4 5).in_groups_of(2, '&nbsp;') {|group| p group}
# ["1", "2"]
- # ["3", "&nbsp;"]
+ # ["3", "4"]
+ # ["5", "&nbsp;"]
#
- # %w(1 2 3).in_groups_of(2, false) {|group| p group}
+ # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group}
# ["1", "2"]
- # ["3"]
+ # ["3", "4"]
+ # ["5"]
def in_groups_of(number, fill_with = nil)
if fill_with == false
collection = self
@@ -42,10 +45,10 @@ class Array
# ["5", "6", "7", nil]
# ["8", "9", "10", nil]
#
- # %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
- # ["1", "2", "3"]
- # ["4", "5", "&nbsp;"]
- # ["6", "7", "&nbsp;"]
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, '&nbsp;') {|group| p group}
+ # ["1", "2", "3", "4"]
+ # ["5", "6", "7", "&nbsp;"]
+ # ["8", "9", "10", "&nbsp;"]
#
# %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
# ["1", "2", "3"]
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 3ec7e576c8..5dc5710c53 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -17,8 +17,13 @@ class BigDecimal
end
DEFAULT_STRING_FORMAT = 'F'
- def to_formatted_s(format = DEFAULT_STRING_FORMAT)
- _original_to_s(format)
+ def to_formatted_s(*args)
+ if args[0].is_a?(Symbol)
+ super
+ else
+ format = args[0] || DEFAULT_STRING_FORMAT
+ _original_to_s(format)
+ end
end
alias_method :_original_to_s, :to_s
alias_method :to_s, :to_formatted_s
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index c64685a694..7b6f8ab0a1 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -65,10 +65,12 @@ class Class
# To opt out of the instance writer method, pass :instance_writer => false.
#
# object.setting = false # => NoMethodError
+ #
+ # To opt out of both instance methods, pass :instance_accessor => false.
def class_attribute(*attrs)
options = attrs.extract_options!
- instance_reader = options.fetch(:instance_reader, true)
- instance_writer = options.fetch(:instance_writer, true)
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
attrs.each do |name|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 74ea047c24..c2e0ebb3d4 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -1,11 +1,11 @@
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
-class Class #:nodoc:
+class Class
begin
ObjectSpace.each_object(Class.new) {}
- def descendants
+ def descendants # :nodoc:
descendants = []
ObjectSpace.each_object(singleton_class) do |k|
descendants.unshift k unless k == self
@@ -13,7 +13,7 @@ class Class #:nodoc:
descendants
end
rescue StandardError # JRuby
- def descendants
+ def descendants # :nodoc:
descendants = []
ObjectSpace.each_object(Class) do |k|
descendants.unshift k if k < self
@@ -25,7 +25,13 @@ class Class #:nodoc:
# Returns an array with the direct children of +self+.
#
- # Integer.subclasses # => [Bignum, Fixnum]
+ # Integer.subclasses # => [Fixnum, Bignum]
+ #
+ # class Foo; end
+ # class Bar < Foo; end
+ # class Baz < Foo; end
+ #
+ # Foo.subclasses # => [Baz, Bar]
def subclasses
subclasses, chain = [], descendants
chain.each do |k|
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 3e36c54eba..7fe4161fb4 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -202,7 +202,18 @@ class Date
acts_like?(:time) ? result.change(:hour => 0) : result
end
- # Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
+ # Short-hand for months_ago(3)
+ def prev_quarter
+ months_ago(3)
+ end
+ alias_method :last_quarter, :prev_quarter
+
+ # Short-hand for months_since(3)
+ def next_quarter
+ months_since(3)
+ end
+
+ # Returns a new Date/DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index 19925198c0..13d659f52a 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -39,7 +39,7 @@ class DateTime
to_default_s
end
end
- alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty?
+ alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
alias_method :to_s, :to_formatted_s
#
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 02d5a7080f..03efe6a19a 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -65,11 +65,15 @@ class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
def sum(identity = 0)
- if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
+ if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
super
else
actual_last = exclude_end? ? (last - 1) : last
- (actual_last - first + 1) * (actual_last + first) / 2
+ if actual_last >= first
+ (actual_last - first + 1) * (actual_last + first) / 2
+ else
+ identity
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 469dc41f2d..7c72ead36c 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -57,8 +57,8 @@ class Hash
# "TrueClass" => "boolean",
# "FalseClass" => "boolean",
# "Date" => "date",
- # "DateTime" => "datetime",
- # "Time" => "datetime"
+ # "DateTime" => "dateTime",
+ # "Time" => "dateTime"
# }
#
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
@@ -129,7 +129,7 @@ class Hash
else
xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }]
- # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
+ # Turn { :files => { :file => #<StringIO> } } into { :files => #<StringIO> } so it is compatible with
# how multipart uploaded files from HTML appear
xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index be4d611ce7..8e728691c6 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,46 +1,59 @@
class Hash
- # Return a new hash with all keys converted to strings.
+ # Return a new hash with all keys converted using the block operation.
#
- # { :name => 'Rob', :years => '28' }.stringify_keys
- # #=> { "name" => "Rob", "years" => "28" }
- def stringify_keys
+ # hash = { name: 'Rob', age: '28' }
+ #
+ # hash.transform_keys{ |key| key.to_s.upcase }
+ # # => { "NAME" => "Rob", "AGE" => "28" }
+ def transform_keys
result = {}
keys.each do |key|
- result[key.to_s] = self[key]
+ result[yield(key)] = self[key]
end
result
end
- # Destructively convert all keys to strings. Same as
- # +stringify_keys+, but modifies +self+.
- def stringify_keys!
+ # Destructively convert all keys using the block operations.
+ # Same as transform_keys but modifies +self+
+ def transform_keys!
keys.each do |key|
- self[key.to_s] = delete(key)
+ self[yield(key)] = delete(key)
end
self
end
+ # Return a new hash with all keys converted to strings.
+ #
+ # hash = { name: 'Rob', age: '28' }
+ #
+ # hash.stringify_keys
+ # #=> { "name" => "Rob", "age" => "28" }
+ def stringify_keys
+ transform_keys{ |key| key.to_s }
+ end
+
+ # Destructively convert all keys to strings. Same as
+ # +stringify_keys+, but modifies +self+.
+ def stringify_keys!
+ transform_keys!{ |key| key.to_s }
+ end
+
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
#
- # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys
- # #=> { :name => "Rob", :years => "28" }
+ # hash = { 'name' => 'Rob', 'age' => '28' }
+ #
+ # hash.symbolize_keys
+ # #=> { name: "Rob", age: "28" }
def symbolize_keys
- result = {}
- keys.each do |key|
- result[(key.to_sym rescue key)] = self[key]
- end
- result
+ transform_keys{ |key| key.to_sym rescue key }
end
alias_method :to_options, :symbolize_keys
# Destructively convert all keys to symbols, as long as they respond
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
def symbolize_keys!
- keys.each do |key|
- self[(key.to_sym rescue key)] = delete(key)
- end
- self
+ transform_keys!{ |key| key.to_sym rescue key }
end
alias_method :to_options!, :symbolize_keys!
@@ -57,4 +70,69 @@ class Hash
raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
end
end
+
+ # Return a new hash with all keys converted by the block operation.
+ # This includes the keys from the root hash and from all
+ # nested hashes.
+ #
+ # hash = { person: { name: 'Rob', age: '28' } }
+ #
+ # hash.deep_transform_keys{ |key| key.to_s.upcase }
+ # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
+ def deep_transform_keys(&block)
+ result = {}
+ each do |key, value|
+ result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
+ end
+ result
+ end
+
+ # Destructively convert all keys by using the block operation.
+ # This includes the keys from the root hash and from all
+ # nested hashes.
+ def deep_transform_keys!(&block)
+ keys.each do |key|
+ value = delete(key)
+ self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
+ end
+ self
+ end
+
+ # Return a new hash with all keys converted to strings.
+ # This includes the keys from the root hash and from all
+ # nested hashes.
+ #
+ # hash = { person: { name: 'Rob', age: '28' } }
+ #
+ # hash.deep_stringify_keys
+ # # => { "person" => { "name" => "Rob", "age" => "28" } }
+ def deep_stringify_keys
+ deep_transform_keys{ |key| key.to_s }
+ end
+
+ # Destructively convert all keys to strings.
+ # This includes the keys from the root hash and from all
+ # nested hashes.
+ def deep_stringify_keys!
+ deep_transform_keys!{ |key| key.to_s }
+ end
+
+ # Return a new hash with all keys converted to symbols, as long as
+ # they respond to +to_sym+. This includes the keys from the root hash
+ # and from all nested hashes.
+ #
+ # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
+ #
+ # hash.deep_symbolize_keys
+ # # => { person: { name: "Rob", age: "28" } }
+ def deep_symbolize_keys
+ deep_transform_keys{ |key| key.to_sym rescue key }
+ end
+
+ # Destructively convert all keys to symbols, as long as they respond
+ # to +to_sym+. This includes the keys from the root hash and from all
+ # nested hashes.
+ def deep_symbolize_keys!
+ deep_transform_keys!{ |key| key.to_sym rescue key }
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 8bdfa0c5bc..fe24f3716d 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -6,12 +6,14 @@ class LoadError
/^cannot load such file -- (.+)$/i,
]
- def path
- @path ||= begin
- REGEXPS.find do |regex|
- message =~ regex
+ unless method_defined?(:path)
+ def path
+ @path ||= begin
+ REGEXPS.find do |regex|
+ message =~ regex
+ end
+ $1
end
- $1
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index f914425827..672cc0256f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -46,19 +46,19 @@ class Module
# Extends the module object with module and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
#
- # module AppConfiguration
- # mattr_accessor :google_api_key
- # self.google_api_key = "123456789"
+ # module AppConfiguration
+ # mattr_accessor :google_api_key
#
- # mattr_accessor :paypal_url
- # self.paypal_url = "www.sandbox.paypal.com"
- # end
+ # self.google_api_key = "123456789"
+ # end
#
- # AppConfiguration.google_api_key = "overriding the api key!"
+ # AppConfiguration.google_api_key # => "123456789"
+ # AppConfiguration.google_api_key = "overriding the api key!"
+ # AppConfiguration.google_api_key # => "overriding the api key!"
#
- # To opt out of the instance writer method, pass :instance_writer => false.
- # To opt out of the instance reader method, pass :instance_reader => false.
- # To opt out of both instance methods, pass :instance_accessor => false.
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
def mattr_accessor(*syms)
mattr_reader(*syms)
mattr_writer(*syms)
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index fbef27c76a..39a1240c61 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -107,7 +107,6 @@ class Module
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter).'
end
- to = to.to_s
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
if prefix == true && to =~ /^[^a-z_]/
@@ -125,8 +124,6 @@ class Module
line = line.to_i
methods.each do |method|
- method = method.to_s
-
# Attribute writer methods only accept one argument. Makes sure []=
# methods still accept two arguments.
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index 3805cf7990..a6bc0624be 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,2 +1,3 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
+require 'active_support/core_ext/numeric/conversions'
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
new file mode 100644
index 0000000000..2bbfa78639
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -0,0 +1,135 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/number_helper'
+
+class Numeric
+
+ # Provides options for converting numbers into formatted strings.
+ # Options are provided for phone numbers, currency, percentage,
+ # precision, positional notation, file size and pretty printing.
+ #
+ # ==== Options
+ #
+ # For details on which formats use which options, see ActiveSupport::NumberHelper
+ #
+ # ==== Examples
+ #
+ # Phone Numbers:
+ # 5551234.to_s(:phone) # => 555-1234
+ # 1235551234.to_s(:phone) # => 123-555-1234
+ # 1235551234.to_s(:phone, :area_code => true) # => (123) 555-1234
+ # 1235551234.to_s(:phone, :delimiter => " ") # => 123 555 1234
+ # 1235551234.to_s(:phone, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
+ # 1235551234.to_s(:phone, :country_code => 1) # => +1-123-555-1234
+ # 1235551234.to_s(:phone, :country_code => 1, :extension => 1343, :delimiter => ".")
+ # # => +1.123.555.1234 x 1343
+ #
+ # Currency:
+ # 1234567890.50.to_s(:currency) # => $1,234,567,890.50
+ # 1234567890.506.to_s(:currency) # => $1,234,567,890.51
+ # 1234567890.506.to_s(:currency, :precision => 3) # => $1,234,567,890.506
+ # 1234567890.506.to_s(:currency, :locale => :fr) # => 1 234 567 890,51 €
+ # -1234567890.50.to_s(:currency, :negative_format => "(%u%n)")
+ # # => ($1,234,567,890.50)
+ # 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => "")
+ # # => &pound;1234567890,50
+ # 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
+ # # => 1234567890,50 &pound;
+ #
+ # Percentage:
+ # 100.to_s(:percentage) # => 100.000%
+ # 100.to_s(:percentage, :precision => 0) # => 100%
+ # 1000.to_s(:percentage, :delimiter => '.', :separator => ',') # => 1.000,000%
+ # 302.24398923423.to_s(:percentage, :precision => 5) # => 302.24399%
+ # 1000.to_s(:percentage, :locale => :fr) # => 1 000,000%
+ # 100.to_s(:percentage, :format => "%n %") # => 100 %
+ #
+ # Delimited:
+ # 12345678.to_s(:delimited) # => 12,345,678
+ # 12345678.05.to_s(:delimited) # => 12,345,678.05
+ # 12345678.to_s(:delimited, :delimiter => ".") # => 12.345.678
+ # 12345678.to_s(:delimited, :delimiter => ",") # => 12,345,678
+ # 12345678.05.to_s(:delimited, :separator => " ") # => 12,345,678 05
+ # 12345678.05.to_s(:delimited, :locale => :fr) # => 12 345 678,05
+ # 98765432.98.to_s(:delimited, :delimiter => " ", :separator => ",")
+ # # => 98 765 432,98
+ #
+ # Rounded:
+ # 111.2345.to_s(:rounded) # => 111.235
+ # 111.2345.to_s(:rounded, :precision => 2) # => 111.23
+ # 13.to_s(:rounded, :precision => 5) # => 13.00000
+ # 389.32314.to_s(:rounded, :precision => 0) # => 389
+ # 111.2345.to_s(:rounded, :significant => true) # => 111
+ # 111.2345.to_s(:rounded, :precision => 1, :significant => true) # => 100
+ # 13.to_s(:rounded, :precision => 5, :significant => true) # => 13.000
+ # 111.234.to_s(:rounded, :locale => :fr) # => 111,234
+ # 13.to_s(:rounded, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
+ # # => 13
+ # 389.32314.to_s(:rounded, :precision => 4, :significant => true) # => 389.3
+ # 1111.2345.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.')
+ # # => 1.111,23
+ #
+ # Human-friendly size in Bytes:
+ # 123.to_s(:human_size) # => 123 Bytes
+ # 1234.to_s(:human_size) # => 1.21 KB
+ # 12345.to_s(:human_size) # => 12.1 KB
+ # 1234567.to_s(:human_size) # => 1.18 MB
+ # 1234567890.to_s(:human_size) # => 1.15 GB
+ # 1234567890123.to_s(:human_size) # => 1.12 TB
+ # 1234567.to_s(:human_size, :precision => 2) # => 1.2 MB
+ # 483989.to_s(:human_size, :precision => 2) # => 470 KB
+ # 1234567.to_s(:human_size, :precision => 2, :separator => ',') # => 1,2 MB
+ # 1234567890123.to_s(:human_size, :precision => 5) # => "1.1229 TB"
+ # 524288000.to_s(:human_size, :precision => 5) # => "500 MB"
+ #
+ # Human-friendly format:
+ # 123.to_s(:human) # => "123"
+ # 1234.to_s(:human) # => "1.23 Thousand"
+ # 12345.to_s(:human) # => "12.3 Thousand"
+ # 1234567.to_s(:human) # => "1.23 Million"
+ # 1234567890.to_s(:human) # => "1.23 Billion"
+ # 1234567890123.to_s(:human) # => "1.23 Trillion"
+ # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+ # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
+ # 489939.to_s(:human, :precision => 2) # => "490 Thousand"
+ # 489939.to_s(:human, :precision => 4) # => "489.9 Thousand"
+ # 1234567.to_s(:human, :precision => 4,
+ # :significant => false) # => "1.2346 Million"
+ # 1234567.to_s(:human, :precision => 1,
+ # :separator => ',',
+ # :significant => false) # => "1,2 Million"
+ def to_formatted_s(format = :default, options = {})
+ case format
+ when :phone
+ return ActiveSupport::NumberHelper.number_to_phone(self, options)
+ when :currency
+ return ActiveSupport::NumberHelper.number_to_currency(self, options)
+ when :percentage
+ return ActiveSupport::NumberHelper.number_to_percentage(self, options)
+ when :delimited
+ return ActiveSupport::NumberHelper.number_to_delimited(self, options)
+ when :rounded
+ return ActiveSupport::NumberHelper.number_to_rounded(self, options)
+ when :human
+ return ActiveSupport::NumberHelper.number_to_human(self, options)
+ when :human_size
+ return ActiveSupport::NumberHelper.number_to_human_size(self, options)
+ else
+ self.to_default_s
+ end
+ end
+
+ [Float, Fixnum, Bignum, BigDecimal].each do |klass|
+ klass.send(:alias_method, :to_default_s, :to_s)
+
+ klass.send(:define_method, :to_s) do |*args|
+ if args[0].is_a?(Symbol)
+ format = args[0]
+ options = args[1] || {}
+
+ self.to_formatted_s(format, options)
+ else
+ to_default_s(*args)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 883f5f556c..f55fbc282e 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/duplicable'
+
class Object
# Returns a deep copy of object if it's duplicable. If it's
# not duplicable, returns +self+.
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 30c835f5cd..16a799ec03 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -11,8 +11,6 @@ class Object
# subclasses of +BasicObject+. For example, using try with +SimpleDelegator+ will
# delegate +try+ to target instead of calling it on delegator itself.
#
- # ==== Examples
- #
# Without +try+
# @person && @person.name
# or
@@ -27,7 +25,7 @@ class Object
#
# Without a method argument try will yield to the block unless the receiver is nil.
# @person.try { |p| "#{p.first_name} #{p.last_name}" }
- #--
+ #
# +try+ behaves like +Object#public_send+, unless called on +NilClass+.
def try(*a, &b)
if a.empty? && block_given?
@@ -42,8 +40,6 @@ class NilClass
# Calling +try+ on +nil+ always returns +nil+.
# It becomes specially helpful when navigating through associations that may return +nil+.
#
- # === Examples
- #
# nil.try(:name) # => nil
#
# Without +try+
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index 5c32a2453d..8fa8157d65 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,5 +1,3 @@
-require 'active_support/multibyte'
-
class String
# If you pass a single Fixnum, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 2478f42290..8644529806 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/multibyte'
-
class String
# Returns the string, first removing all whitespace on both ends of
# the string, and then changing remaining consecutive whitespace
@@ -33,7 +31,7 @@ class String
# # => "Once upon a time in a..."
#
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
- # for a total length not exceeding <tt>:length</tt>:
+ # for a total length not exceeding <tt>length</tt>:
#
# 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)')
# # => "And they f... (continued)"
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 070bfd7af6..efa2d43f20 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -107,7 +107,7 @@ class String
# Replaces underscores with dashes in the string.
#
- # 'puni_puni' # => "puni-puni"
+ # 'puni_puni'.dasherize # => "puni-puni"
def dasherize
ActiveSupport::Inflector.dasherize(self)
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 92b8417150..28c8b53b78 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -186,6 +186,17 @@ class Time
months_since(1)
end
+ # Short-hand for months_ago(3)
+ def prev_quarter
+ months_ago(3)
+ end
+ alias_method :last_quarter, :prev_quarter
+
+ # Short-hand for months_since(3)
+ def next_quarter
+ months_since(3)
+ end
+
# Returns number of days to start of this week, week starts on start_day (default is :monday).
def days_to_week_start(start_day = :monday)
start_day_number = DAYS_INTO_WEEK[start_day]
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 66f3af7002..c9071a73d8 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -168,8 +168,6 @@ module ActiveSupport #:nodoc:
end
end
- # Use const_missing to autoload associations so we don't have to
- # require_association when using single-table inheritance.
def const_missing(const_name, nesting = nil)
klass_name = name.presence || "Object"
@@ -220,11 +218,7 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}"
end
- Dependencies.depend_on(file_name, false, message)
- end
-
- def require_association(file_name)
- Dependencies.associate_with(file_name)
+ Dependencies.depend_on(file_name, message)
end
def load_dependency(file)
@@ -306,20 +300,15 @@ module ActiveSupport #:nodoc:
mechanism == :load
end
- def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb")
+ def depend_on(file_name, message = "No such file to load -- %s.rb")
path = search_for_file(file_name)
require_or_load(path || file_name)
rescue LoadError => load_error
- unless swallow_load_errors
- if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
- raise LoadError.new(message % file_name).copy_blame!(load_error)
- end
- raise
+ if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
+ load_error.message.replace(message % file_name)
+ load_error.copy_blame!(load_error)
end
- end
-
- def associate_with(file_name)
- depend_on(file_name, true)
+ raise
end
def clear
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index a1626ebeba..4045db3232 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -1,5 +1,4 @@
require "active_support/inflector/methods"
-require "active_support/lazy_load_hooks"
module ActiveSupport
module Autoload
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 176edefa42..e3b4a7240e 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -10,10 +10,10 @@ module ActiveSupport
# The version the deprecated behavior will be removed, by default.
attr_accessor :deprecation_horizon
end
- self.deprecation_horizon = '3.2'
+ self.deprecation_horizon = '4.1'
# By default, warnings are not silenced and debugging is off.
self.silenced = false
self.debug = false
end
-end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 8860636168..48c39d9370 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -35,22 +35,11 @@ module ActiveSupport
# have directories as keys and the value is an array of extensions to be
# watched under that directory.
#
- # This method must also receive a block that will be called once a path changes.
- #
- # == Implementation details
- #
- # This particular implementation checks for added, updated, and removed
- # files. Directories lookup are compiled to a glob for performance.
- # Therefore, while someone can add new files to the +files+ array after
- # initialization (and parts of Rails do depend on this feature), adding
- # new directories after initialization is not supported.
- #
- # Notice that other objects that implement the FileUpdateChecker API may
- # not even allow new files to be added after initialization. If this
- # is the case, we recommend freezing the +files+ after initialization to
- # avoid changes that won't make effect.
+ # This method must also receive a block that will be called once a path
+ # changes. The array of files and list of directories cannot be changed
+ # after FileUpdateChecker has been initialized.
def initialize(files, dirs={}, &block)
- @files = files
+ @files = files.freeze
@glob = compile_glob(dirs)
@block = block
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 420b965c87..6ef33ab683 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -2,7 +2,14 @@ require 'zlib'
require 'stringio'
module ActiveSupport
- # A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip.
+ # A convenient wrapper for the zlib standard library that allows
+ # compression/decompression of strings with gzip.
+ #
+ # gzip = ActiveSupport::Gzip.compress('compress me!')
+ # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00"
+ #
+ # ActiveSupport::Gzip.decompress(gzip)
+ # # => "compress me!"
module Gzip
class Stream < StringIO
def initialize(*)
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 91459f3e5b..3e6c8893e9 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -141,9 +141,13 @@ module ActiveSupport
end
def stringify_keys!; self end
+ def deep_stringify_keys!; self end
def stringify_keys; dup end
+ def deep_stringify_keys; dup end
undef :symbolize_keys!
+ undef :deep_symbolize_keys!
def symbolize_keys; to_hash.symbolize_keys end
+ def deep_symbolize_keys; to_hash.deep_symbolize_keys end
def to_options!; self end
# Convert to a Hash with String keys.
@@ -160,7 +164,8 @@ module ActiveSupport
if value.is_a? Hash
value.nested_under_indifferent_access
elsif value.is_a?(Array)
- value.dup.replace(value.map { |e| convert_value(e) })
+ value = value.dup if value.frozen?
+ value.map! { |e| convert_value(e) }
else
value
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index bbeb8d82c6..f67b221024 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -9,25 +9,6 @@ module I18n
config.i18n.load_path = []
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
- def self.reloader
- @reloader ||= ActiveSupport::FileUpdateChecker.new(reloader_paths){ I18n.reload! }
- end
-
- def self.reloader_paths
- @reloader_paths ||= []
- end
-
- # Add <tt>I18n::Railtie.reloader</tt> to ActionDispatch callbacks. Since, at this
- # point, no path was added to the reloader, I18n.reload! is not triggered
- # on to_prepare callbacks. This will only happen on the config.after_initialize
- # callback below.
- initializer "i18n.callbacks" do |app|
- app.reloaders << I18n::Railtie.reloader
- ActionDispatch::Reloader.to_prepare do
- I18n::Railtie.reloader.execute_if_updated
- end
- end
-
# Set the i18n configuration after initialization since a lot of
# configuration is still usually done in application initializers.
config.after_initialize do |app|
@@ -63,7 +44,9 @@ module I18n
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
- reloader_paths.concat I18n.load_path
+ reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
+ app.reloaders << reloader
+ ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
reloader.execute
@i18n_inited = true
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 986a764479..e44939e78a 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -8,6 +8,11 @@ module ActiveSupport
module JSON
class << self
+ # Parses a JSON string (JavaScript Object Notation) into a hash.
+ # See www.json.org for more info.
+ #
+ # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
+ # => {"team" => "rails", "players" => "36"}
def decode(json, options ={})
data = MultiJson.load(json, options)
if ActiveSupport.parse_json_times
@@ -34,6 +39,14 @@ module ActiveSupport
self.backend = old_backend
end
+ # Returns the class of the error that will be raised when there is an error in decoding JSON.
+ # Using this method means you won't directly depend on the ActiveSupport's JSON implementation, in case it changes in the future.
+ #
+ # begin
+ # obj = ActiveSupport::JSON.decode(some_string)
+ # rescue ActiveSupport::JSON.parse_error
+ # Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}")
+ # end
def parse_error
MultiJson::DecodeError
end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index a6e4e7ced2..c319e94bc6 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
-require 'active_support/json/variable'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
@@ -25,7 +24,10 @@ module ActiveSupport
# matches YAML-formatted dates
DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
- # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
+ # Dumps objects in JSON (JavaScript Object Notation). See www.json.org for more info.
+ #
+ # ActiveSupport::JSON.encode({team: 'rails', players: '36'})
+ # # => "{\"team\":\"rails\",\"players\":\"36\"}"
def self.encode(value, options = nil)
Encoding::Encoder.new(options).encode(value)
end
@@ -159,18 +161,18 @@ class Struct #:nodoc:
end
class TrueClass
- AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze
- def as_json(options = nil) AS_JSON end #:nodoc:
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) to_s end #:nodoc:
end
class FalseClass
- AS_JSON = ActiveSupport::JSON::Variable.new('false').freeze
- def as_json(options = nil) AS_JSON end #:nodoc:
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) to_s end #:nodoc:
end
class NilClass
- AS_JSON = ActiveSupport::JSON::Variable.new('null').freeze
- def as_json(options = nil) AS_JSON end #:nodoc:
+ def as_json(options = nil) self end #:nodoc:
+ def encode_json(encoder) 'null' end #:nodoc:
end
class String
@@ -189,8 +191,8 @@ end
class Float
# Encoding Infinity or NaN to JSON should return "null". The default returns
- # "Infinity" or "NaN" what breaks parsing the JSON. E.g. JSON.parse('[NaN]').
- def as_json(options = nil) finite? ? self : NilClass::AS_JSON end #:nodoc:
+ # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
+ def as_json(options = nil) finite? ? self : nil end #:nodoc:
end
class BigDecimal
@@ -208,7 +210,7 @@ class BigDecimal
if finite?
ActiveSupport.encode_big_decimal_as_string ? to_s : self
else
- NilClass::AS_JSON
+ nil
end
end
end
diff --git a/activesupport/lib/active_support/json/variable.rb b/activesupport/lib/active_support/json/variable.rb
deleted file mode 100644
index 5685ed18b7..0000000000
--- a/activesupport/lib/active_support/json/variable.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module ActiveSupport
- module JSON
- # A string that returns itself as its JSON-encoded form.
- class Variable < String
- def as_json(options = nil) self end #:nodoc:
- def encode_json(encoder) self end #:nodoc:
- end
- end
-end
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index a1499bcc90..18c7d47026 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -34,3 +34,102 @@ en:
words_connector: ", "
two_words_connector: " and "
last_word_connector: ", and "
+ number:
+ # Used in NumberHelper.number_to_delimited()
+ # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
+ format:
+ # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
+ separator: "."
+ # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
+ delimiter: ","
+ # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
+ precision: 3
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_currency()
+ currency:
+ format:
+ # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
+ format: "%u%n"
+ unit: "$"
+ # These five are to override number.format and are optional
+ separator: "."
+ delimiter: ","
+ precision: 2
+ significant: false
+ strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_percentage()
+ percentage:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+ format: "%n%"
+
+ # Used in NumberHelper.number_to_rounded()
+ precision:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ # precision:
+ # significant: false
+ # strip_insignificant_zeros: false
+
+ # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human()
+ human:
+ format:
+ # These five are to override number.format and are optional
+ # separator:
+ delimiter: ""
+ precision: 3
+ significant: true
+ strip_insignificant_zeros: true
+ # Used in number_to_human_size()
+ storage_units:
+ # Storage units output formatting.
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+ # Used in NumberHelper.number_to_human()
+ decimal_units:
+ format: "%n %u"
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units:
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: ""
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: Thousand
+ million: Million
+ billion: Billion
+ trillion: Trillion
+ quadrillion: Quadrillion
+
+ \ No newline at end of file
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index d2a6e1bd82..e5b4ca2738 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -48,20 +48,19 @@ module ActiveSupport
mattr_accessor :colorize_logging
self.colorize_logging = true
- class_attribute :logger
-
class << self
- remove_method :logger
def logger
@logger ||= Rails.logger if defined?(Rails)
+ @logger
end
+ attr_writer :logger
+
def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
log_subscribers << log_subscriber
- @@flushable_loggers = nil
log_subscriber.public_methods(false).each do |event|
- next if 'call' == event.to_s
+ next if %w{ start finish }.include?(event.to_s)
notifier.subscribe("#{event}.#{namespace}", log_subscriber)
end
@@ -71,29 +70,44 @@ module ActiveSupport
@@log_subscribers ||= []
end
- def flushable_loggers
- @@flushable_loggers ||= begin
- loggers = log_subscribers.map(&:logger)
- loggers.uniq!
- loggers.select! { |l| l.respond_to?(:flush) }
- loggers
- end
- end
-
# Flush all log_subscribers' logger.
def flush_all!
- flushable_loggers.each { |log| log.flush }
+ logger.flush if logger.respond_to?(:flush)
end
end
- def call(message, *args)
+ def initialize
+ @queue_key = [self.class.name, object_id].join "-"
+ super
+ end
+
+ def logger
+ LogSubscriber.logger
+ end
+
+ def start(name, id, payload)
return unless logger
- method = message.split('.').first
+ e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
+ parent = event_stack.last
+ parent << e if parent
+
+ event_stack.push e
+ end
+
+ def finish(name, id, payload)
+ return unless logger
+
+ finished = Time.now
+ event = event_stack.pop
+ event.end = finished
+ event.payload.merge!(payload)
+
+ method = name.split('.').first
begin
- send(method, ActiveSupport::Notifications::Event.new(message, *args))
+ send(method, event)
rescue Exception => e
- logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
end
@@ -114,9 +128,15 @@ module ActiveSupport
#
def color(text, color, bold=false)
return text unless colorize_logging
- color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
+ color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
bold = bold ? BOLD : ""
"#{bold}#{color}#{text}#{CLEAR}"
end
+
+ private
+
+ def event_stack
+ Thread.current[@queue_key] ||= []
+ end
end
end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 4fe925f7f4..47336d2143 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -76,7 +76,7 @@ module ActiveSupport #:nodoc:
#
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
def split(*args)
- @wrapped_string.split(*args).map { |i| i.mb_chars }
+ @wrapped_string.split(*args).map { |i| self.class.new(i) }
end
# Works like like <tt>String#slice!</tt>, but returns an instance of Chars, or nil if the string was not
@@ -133,7 +133,7 @@ module ActiveSupport #:nodoc:
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
# "日本語".mb_chars.titleize # => "日本語"
def titleize
- chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.upcase($1)})
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
end
alias_method :titlecase, :titleize
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 678f551193..ef1711c60a 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -331,7 +331,7 @@ module ActiveSupport
def load
begin
@codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, 'rb') { |f| Marshal.load f.read }
- rescue Exception => e
+ rescue => e
raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable")
end
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 6735c561d3..b4657a8ba9 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -33,7 +33,7 @@ module ActiveSupport
# end
#
# That code returns right away, you are just subscribing to "render" events.
- # The block will be called asynchronously whenever someone instruments "render":
+ # The block is saved and will be called whenever someone instruments "render":
#
# ActiveSupport::Notifications.instrument("render", :extra => :information) do
# render :text => "Foo"
@@ -135,8 +135,6 @@ module ActiveSupport
# to log subscribers in a thread. You can use any queue implementation you want.
#
module Notifications
- @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) }
-
class << self
attr_accessor :notifier
@@ -145,7 +143,7 @@ module ActiveSupport
end
def instrument(name, payload = {})
- if @instrumenters[name]
+ if notifier.listening?(name)
instrumenter.instrument(name, payload) { yield payload if block_given? }
else
yield payload if block_given?
@@ -153,9 +151,7 @@ module ActiveSupport
end
def subscribe(*args, &block)
- notifier.subscribe(*args, &block).tap do
- @instrumenters.clear
- end
+ notifier.subscribe(*args, &block)
end
def subscribed(callback, *args, &block)
@@ -167,7 +163,6 @@ module ActiveSupport
def unsubscribe(args)
notifier.unsubscribe(args)
- @instrumenters.clear
end
def instrumenter
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 17c99089c1..6ffc091233 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,23 +1,34 @@
+require 'mutex_m'
+
module ActiveSupport
module Notifications
# This is a default queue implementation that ships with Notifications.
# It just pushes events to all registered log subscribers.
+ #
+ # This class is thread safe. All methods are reentrant.
class Fanout
+ include Mutex_m
+
def initialize
@subscribers = []
@listeners_for = {}
+ super
end
def subscribe(pattern = nil, block = Proc.new)
subscriber = Subscribers.new pattern, block
- @subscribers << subscriber
- @listeners_for.clear
+ synchronize do
+ @subscribers << subscriber
+ @listeners_for.clear
+ end
subscriber
end
def unsubscribe(subscriber)
- @subscribers.reject! { |s| s.matches?(subscriber) }
- @listeners_for.clear
+ synchronize do
+ @subscribers.reject! { |s| s.matches?(subscriber) }
+ @listeners_for.clear
+ end
end
def start(name, id, payload)
@@ -33,7 +44,9 @@ module ActiveSupport
end
def listeners_for(name)
- @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
+ synchronize do
+ @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
+ end
end
def listening?(name)
@@ -85,9 +98,7 @@ module ActiveSupport
class Timed < Evented
def initialize(pattern, delegate)
- @timestack = Hash.new { |h,id|
- h[id] = Hash.new { |ids,name| ids[name] = [] }
- }
+ @timestack = []
super
end
@@ -96,11 +107,11 @@ module ActiveSupport
end
def start(name, id, payload)
- @timestack[id][name].push Time.now
+ @timestack.push Time.now
end
def finish(name, id, payload)
- started = @timestack[id][name].pop
+ started = @timestack.pop
@delegate.call(name, started, Time.now, id, payload)
end
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 58e292c658..78d0397f1f 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -1,3 +1,5 @@
+require 'securerandom'
+
module ActiveSupport
module Notifications
# Instrumentors are stored in a thread local.
@@ -31,7 +33,8 @@ module ActiveSupport
end
class Event
- attr_reader :name, :time, :end, :transaction_id, :payload, :duration
+ attr_reader :name, :time, :transaction_id, :payload, :children
+ attr_accessor :end
def initialize(name, start, ending, transaction_id, payload)
@name = name
@@ -39,12 +42,19 @@ module ActiveSupport
@time = start
@transaction_id = transaction_id
@end = ending
- @duration = 1000.0 * (@end - @time)
+ @children = []
+ end
+
+ def duration
+ 1000.0 * (self.end - time)
+ end
+
+ def <<(event)
+ @children << event
end
def parent_of?(event)
- start = (time - event.time) * 1000
- start <= 0 && (start + duration >= event.duration)
+ @children.include? event
end
end
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
new file mode 100644
index 0000000000..c736041066
--- /dev/null
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -0,0 +1,532 @@
+require 'active_support/core_ext/big_decimal/conversions'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+require 'active_support/i18n'
+
+module ActiveSupport
+ module NumberHelper
+ extend self
+
+ DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
+ :precision => 2, :significant => false, :strip_insignificant_zeros => false }
+
+ # Formats a +number+ into a US phone number (e.g., (555)
+ # 123-9876). You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
+ # (defaults to "-").
+ # * <tt>:extension</tt> - Specifies an extension to add to the
+ # end of the generated number.
+ # * <tt>:country_code</tt> - Sets the country code for the phone
+ # number.
+ # ==== Examples
+ #
+ # number_to_phone(5551234) # => 555-1234
+ # number_to_phone("5551234") # => 555-1234
+ # number_to_phone(1235551234) # => 123-555-1234
+ # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
+ # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
+ # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
+ # number_to_phone("123a456") # => 123a456
+ #
+ # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
+ # # => +1.123.555.1234 x 1343
+ def number_to_phone(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ number = number.to_s.strip
+ area_code = options[:area_code]
+ delimiter = options[:delimiter] || "-"
+ extension = options[:extension]
+ country_code = options[:country_code]
+
+ if area_code
+ number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ else
+ number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
+ end
+
+ str = ''
+ str << "+#{country_code}#{delimiter}" unless country_code.blank?
+ str << number
+ str << " x #{extension}" unless extension.blank?
+ str
+ end
+
+ # Formats a +number+ into a currency string (e.g., $13.65). You
+ # can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
+ # to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency
+ # (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units
+ # (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
+ # currency, and <tt>%n</tt> for the number.
+ # * <tt>:negative_format</tt> - Sets the format for negative
+ # numbers (defaults to prepending an hyphen to the formatted
+ # number given by <tt>:format</tt>). Accepts the same fields
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
+ # absolute value of the number.
+ #
+ # ==== Examples
+ #
+ # number_to_currency(1234567890.50) # => $1,234,567,890.50
+ # number_to_currency(1234567890.506) # => $1,234,567,890.51
+ # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
+ # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
+ # number_to_currency('123a456') # => $123a456
+ #
+ # number_to_currency(-1234567890.50, negative_format: '(%u%n)')
+ # # => ($1,234,567,890.50)
+ # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '')
+ # # => &pound;1234567890,50
+ # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
+ # # => 1234567890,50 &pound;
+ def number_to_currency(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ currency = translations_for('currency', options[:locale])
+ currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
+
+ defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
+ defaults[:negative_format] = "-" + options[:format] if options[:format]
+ options = defaults.merge!(options)
+
+ unit = options.delete(:unit)
+ format = options.delete(:format)
+
+ if number.to_f.phase != 0
+ format = options.delete(:negative_format)
+ number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
+ end
+
+ formatted_number = format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit)
+ formatted_number
+ end
+
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
+ # customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
+ #
+ # ==== Examples
+ #
+ # number_to_percentage(100) # => 100.000%
+ # number_to_percentage('98') # => 98.000%
+ # number_to_percentage(100, precision: 0) # => 100%
+ # number_to_percentage(1000, delimiter: '.', separator: ,') # => 1.000,000%
+ # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
+ # number_to_percentage(1000, :locale => :fr) # => 1 000,000%
+ # number_to_percentage('98a') # => 98a%
+ # number_to_percentage(100, format: '%n %') # => 100 %
+ def number_to_percentage(number, options = {})
+ return unless number
+ options = options.symbolize_keys
+
+ defaults = format_translations('percentage', options[:locale])
+ options = defaults.merge!(options)
+
+ format = options[:format] || "%n%"
+
+ formatted_number = format.gsub('%n', self.number_to_rounded(number, options))
+ formatted_number
+ end
+
+ # Formats a +number+ with grouped thousands using +delimiter+
+ # (e.g., 12,324). You can customize the format in the +options+
+ # hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ #
+ # ==== Examples
+ #
+ # number_to_delimited(12345678) # => 12,345,678
+ # number_to_delimited('123456') # => 123,456
+ # number_to_delimited(12345678.05) # => 12,345,678.05
+ # number_to_delimited(12345678, delimiter: '.') # => 12.345.678
+ # number_to_delimited(12345678, delimiter: ',') # => 12,345,678
+ # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05
+ # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
+ # number_to_delimited('112a') # => 112a
+ # number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
+ # # => 98 765 432,98
+ def number_to_delimited(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+
+ options = defaults_translations(options[:locale]).merge(options)
+
+ parts = number.to_s.to_str.split('.')
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
+ parts.join(options[:separator])
+ end
+
+ # Formats a +number+ with the specified level of
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
+ # You can customize the format in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ #
+ # ==== Examples
+ #
+ # number_to_rounded(111.2345) # => 111.235
+ # number_to_rounded(111.2345, precision: 2) # => 111.23
+ # number_to_rounded(13, precision: 5) # => 13.00000
+ # number_to_rounded(389.32314, precision: 0) # => 389
+ # number_to_rounded(111.2345, significant: true) # => 111
+ # number_to_rounded(111.2345, precision: 1, significant: true) # => 100
+ # number_to_rounded(13, precision: 5, significant: true) # => 13.000
+ # number_to_rounded(111.234, locale: :fr) # => 111,234
+ #
+ # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
+ # # => 13
+ #
+ # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3
+ # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
+ # # => 1.111,23
+ def number_to_rounded(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('precision', options[:locale])
+ options = defaults.merge!(options)
+
+ precision = options.delete :precision
+ significant = options.delete :significant
+ strip_insignificant_zeros = options.delete :strip_insignificant_zeros
+
+ if significant and precision > 0
+ if number == 0
+ digits, rounded_number = 1, 0
+ else
+ digits = (Math.log10(number.abs) + 1).floor
+ rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
+ digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
+ end
+ precision -= digits
+ precision = precision > 0 ? precision : 0 #don't let it be negative
+ else
+ rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
+ rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
+ end
+ formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options)
+ if strip_insignificant_zeros
+ escaped_separator = Regexp.escape(options[:separator])
+ formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
+ else
+ formatted_number
+ end
+ end
+
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
+
+ # Formats the bytes in +number+ into a more understandable
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
+ # method is useful for reporting file sizes to users. You can
+ # customize the format in the +options+ hash.
+ #
+ # See <tt>number_to_human</tt> if you want to pretty-print a
+ # generic number.
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
+ # prefix (defaults to :binary)
+ #
+ # ==== Examples
+ #
+ # number_to_human_size(123) # => 123 Bytes
+ # number_to_human_size(1234) # => 1.21 KB
+ # number_to_human_size(12345) # => 12.1 KB
+ # number_to_human_size(1234567) # => 1.18 MB
+ # number_to_human_size(1234567890) # => 1.15 GB
+ # number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567, precision: 2) # => 1.2 MB
+ # number_to_human_size(483989, precision: 2) # => 470 KB
+ # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
+ #
+ # Non-significant zeros after the fractional separator are stripped out by
+ # default (set <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ #
+ # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB"
+ # number_to_human_size(524288000, precision: 5) # => "500 MB"
+ def number_to_human_size(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
+
+ #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)
+
+ storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
+
+ base = options[:prefix] == :si ? 1000 : 1024
+
+ if number.to_i < base
+ unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
+ else
+ max_exp = STORAGE_UNITS.size - 1
+ exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
+ number /= base ** exponent
+
+ unit_key = STORAGE_UNITS[exponent]
+ unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
+
+ formatted_number = self.number_to_rounded(number, options)
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
+ end
+ end
+
+ 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}.freeze
+
+ # Pretty prints (formats and approximates) a number in a way it
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
+ # Billion"). This is useful for numbers that can get very large
+ # (and too hard to read).
+ #
+ # 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
+ # 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
+ # (centi, deci, mili, etc).
+ #
+ # ==== Options
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
+ # string containing an i18n scope where to find this hash. It
+ # might have the following keys:
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
+ # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
+ # *<tt>:billion</tt>, <tt>:trillion</tt>,
+ # *<tt>:quadrillion</tt>
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # *<tt>:pico</tt>, <tt>:femto</tt>
+ # * <tt>:format</tt> - Sets the format of the output string
+ # (defaults to "%n %u"). The field types are:
+ # * %u - The quantifier (ex.: 'thousand')
+ # * %n - The number
+ #
+ # ==== Examples
+ #
+ # number_to_human(123) # => "123"
+ # number_to_human(1234) # => "1.23 Thousand"
+ # number_to_human(12345) # => "12.3 Thousand"
+ # number_to_human(1234567) # => "1.23 Million"
+ # number_to_human(1234567890) # => "1.23 Billion"
+ # number_to_human(1234567890123) # => "1.23 Trillion"
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
+ # number_to_human(489939, precision: 2) # => "490 Thousand"
+ # number_to_human(489939, precision: 4) # => "489.9 Thousand"
+ # number_to_human(1234567, precision: 4,
+ # significant: false) # => "1.2346 Million"
+ # number_to_human(1234567, precision: 1,
+ # separator: ',',
+ # significant: false) # => "1,2 Million"
+ #
+ # Non-significant zeros after the decimal separator are stripped
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
+ # +false+ to change that):
+ #
+ # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
+ # number_to_human(500000000, precision: 5) # => "500 Million"
+ #
+ # ==== Custom Unit Quantifiers
+ #
+ # You can also use your own custom unit quantifiers:
+ # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
+ #
+ # If in your I18n locale you have:
+ #
+ # distance:
+ # centi:
+ # one: "centimeter"
+ # other: "centimeters"
+ # unit:
+ # one: "meter"
+ # other: "meters"
+ # thousand:
+ # one: "kilometer"
+ # other: "kilometers"
+ # billion: "gazillion-distance"
+ #
+ # Then you could do:
+ #
+ # number_to_human(543934, :units => :distance) # => "544 kilometers"
+ # number_to_human(54393498, :units => :distance) # => "54400 kilometers"
+ # number_to_human(54393498000, :units => :distance) # => "54.4 gazillion-distance"
+ # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
+ # number_to_human(1, :units => :distance) # => "1 meter"
+ # number_to_human(0.34, :units => :distance) # => "34 centimeters"
+ def number_to_human(number, options = {})
+ options = options.symbolize_keys
+
+ return number unless valid_float?(number)
+ number = Float(number)
+
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
+
+ #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
+ units
+ when String, Symbol
+ I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
+ when nil
+ I18n.translate(:"number.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}
+
+ number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
+ display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
+ number /= 10 ** display_exponent
+
+ unit = case units
+ when Hash
+ units[DECIMAL_UNITS[display_exponent]]
+ when String, Symbol
+ I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ else
+ I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ end
+
+ decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
+ formatted_number = self.number_to_rounded(number, options)
+ decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip
+ end
+
+ def self.private_module_and_instance_method(method_name)
+ private method_name
+ private_class_method method_name
+ end
+ private_class_method :private_module_and_instance_method
+
+ def format_translations(namespace, locale) #:nodoc:
+ defaults_translations(locale).merge(translations_for(namespace, locale))
+ end
+ private_module_and_instance_method :format_translations
+
+ def defaults_translations(locale) #:nodoc:
+ I18n.translate(:'number.format', :locale => locale, :default => {})
+ end
+ private_module_and_instance_method :defaults_translations
+
+ def translations_for(namespace, locale) #:nodoc:
+ I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ end
+ private_module_and_instance_method :translations_for
+
+ def valid_float?(number) #:nodoc:
+ Float(number)
+ rescue ArgumentError, TypeError
+ false
+ end
+ private_module_and_instance_method :valid_float?
+
+ end
+end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 9a52c916ec..14ceb7072e 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -22,8 +22,7 @@ module ActiveSupport
end
Assertion = MiniTest::Assertion
- alias_method :method_name, :name if method_defined? :name
- alias_method :method_name, :__name__ if method_defined? :__name__
+ alias_method :method_name, :__name__
$tags = {}
def self.for_tag(tag)
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 1a0681e850..50ec50ca52 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -33,6 +33,45 @@ module ActiveSupport
end
module Isolation
+ require 'thread'
+
+ class ParallelEach
+ include Enumerable
+
+ # default to 2 cores
+ CORES = (ENV['TEST_CORES'] || 2).to_i
+
+ def initialize list
+ @list = list
+ @queue = SizedQueue.new CORES
+ end
+
+ def grep pattern
+ self.class.new super
+ end
+
+ def each
+ threads = CORES.times.map {
+ Thread.new {
+ while job = @queue.pop
+ yield job
+ end
+ }
+ }
+ @list.each { |i| @queue << i }
+ CORES.times { @queue << nil }
+ threads.each(&:join)
+ end
+ end
+
+ def self.included klass
+ klass.extend(Module.new {
+ def test_methods
+ ParallelEach.new super
+ end
+ })
+ end
+
def self.forking_env?
!ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
end
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 2bea0f991a..a6c57cd0ff 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -1,9 +1,9 @@
require 'fileutils'
-require 'rails/version'
require 'active_support/concern'
require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/string/inflections'
-require 'action_view/helpers/number_helper'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/number_helper'
module ActiveSupport
module Testing
@@ -148,26 +148,20 @@ module ActiveSupport
end
def environment
- unless defined? @env
- app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
-
- rails = Rails::VERSION::STRING
- if File.directory?('vendor/rails/.git')
- Dir.chdir('vendor/rails') do
- rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
- end
- end
-
- ruby = "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
-
- @env = [app, rails, ruby, RUBY_PLATFORM] * ','
- end
-
- @env
+ @env ||= [].tap do |env|
+ env << "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
+ env << rails_version if defined?(Rails::VERSION::STRING)
+ env << "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
+ env << RUBY_PLATFORM
+ end.join(',')
end
protected
- HEADER = 'measurement,created_at,app,rails,ruby,platform'
+ if defined?(Rails::VERSION::STRING)
+ HEADER = 'measurement,created_at,app,rails,ruby,platform'
+ else
+ HEADER = 'measurement,created_at,app,ruby,platform'
+ end
def with_output_file
fname = output_filename
@@ -185,6 +179,18 @@ module ActiveSupport
def output_filename
"#{super}.csv"
end
+
+ def rails_version
+ "rails-#{Rails::VERSION::STRING}#{rails_branch}"
+ end
+
+ def rails_branch
+ if File.directory?('vendor/rails/.git')
+ Dir.chdir('vendor/rails') do
+ ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
+ end
+ end
+ end
end
module Metrics
@@ -195,8 +201,7 @@ module ActiveSupport
end
class Base
- include ActionView::Helpers::NumberHelper
- include ActionView::Helpers::OutputSafetyHelper
+ include ActiveSupport::NumberHelper
attr_reader :total
@@ -240,7 +245,7 @@ module ActiveSupport
class Amount < Base
def format(measurement)
- number_with_delimiter(measurement.floor)
+ number_to_delimited(measurement.floor)
end
end
diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb
index 1104fc0a03..12aef0d7fe 100644
--- a/activesupport/lib/active_support/testing/performance/ruby.rb
+++ b/activesupport/lib/active_support/testing/performance/ruby.rb
@@ -18,6 +18,7 @@ module ActiveSupport
end).freeze
protected
+ remove_method :run_gc
def run_gc
GC.start
end
@@ -28,6 +29,7 @@ module ActiveSupport
@supported = @metric.measure_mode rescue false
end
+ remove_method :run
def run
return unless @supported
@@ -39,6 +41,7 @@ module ActiveSupport
@total = @data.threads.sum(0) { |thread| thread.methods.max.total_time }
end
+ remove_method :record
def record
return unless @supported
@@ -78,6 +81,7 @@ module ActiveSupport
self.class::Mode
end
+ remove_method :profile
def profile
RubyProf.resume
yield
@@ -86,6 +90,7 @@ module ActiveSupport
end
protected
+ remove_method :with_gc_stats
def with_gc_stats
GC::Profiler.enable
GC.start
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 772c7b4209..527fa555b7 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -4,6 +4,14 @@ require 'active_support/callbacks'
module ActiveSupport
module Testing
module SetupAndTeardown
+
+ PASSTHROUGH_EXCEPTIONS = [
+ NoMemoryError,
+ SignalException,
+ Interrupt,
+ SystemExit
+ ]
+
extend ActiveSupport::Concern
included do
@@ -28,11 +36,15 @@ module ActiveSupport
run_callbacks :setup do
result = super
end
+ rescue *PASSTHROUGH_EXCEPTIONS
+ raise
rescue Exception => e
result = runner.puke(self.class, method_name, e)
ensure
begin
run_callbacks :teardown
+ rescue *PASSTHROUGH_EXCEPTIONS
+ raise
rescue Exception => e
result = runner.puke(self.class, method_name, e)
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 677e9910bb..88f9acb588 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -39,8 +39,8 @@ module ActiveSupport
"TrueClass" => "boolean",
"FalseClass" => "boolean",
"Date" => "date",
- "DateTime" => "datetime",
- "Time" => "datetime",
+ "DateTime" => "dateTime",
+ "Time" => "dateTime",
"Array" => "array",
"Hash" => "hash"
} unless defined?(TYPE_NAMES)
@@ -48,7 +48,7 @@ module ActiveSupport
FORMATTING = {
"symbol" => Proc.new { |symbol| symbol.to_s },
"date" => Proc.new { |date| date.to_s(:db) },
- "datetime" => Proc.new { |time| time.xmlschema },
+ "dateTime" => Proc.new { |time| time.xmlschema },
"binary" => Proc.new { |binary| ::Base64.encode64(binary) },
"yaml" => Proc.new { |yaml| yaml.to_yaml }
} unless defined?(FORMATTING)
@@ -83,7 +83,7 @@ module ActiveSupport
if name.is_a?(Module)
@backend = name
else
- require "active_support/xml_mini/#{name.to_s.downcase}"
+ require "active_support/xml_mini/#{name.downcase}"
@backend = ActiveSupport.const_get("XmlMini_#{name}")
end
end
@@ -111,6 +111,7 @@ module ActiveSupport
type_name ||= TYPE_NAMES[value.class.name]
type_name ||= value.class.name if value && !value.respond_to?(:to_str)
type_name = type_name.to_s if type_name
+ type_name = "dateTime" if type_name == "datetime"
key = rename_key(key.to_s, options)
@@ -145,7 +146,7 @@ module ActiveSupport
"#{left}#{middle.tr('_ ', '--')}#{right}"
end
- # TODO: Add support for other encodings
+ # TODO: Add support for other encodings
def _parse_binary(bin, entity) #:nodoc:
case entity['encoding']
when 'base64'
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index d62b782e2d..a75db47be8 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -553,6 +553,9 @@ class FileStoreTest < ActiveSupport::TestCase
@cache = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60)
@peek = ActiveSupport::Cache.lookup_store(:file_store, cache_dir, :expires_in => 60)
@cache_with_pathname = ActiveSupport::Cache.lookup_store(:file_store, Pathname.new(cache_dir), :expires_in => 60)
+
+ @buffer = StringIO.new
+ @cache.logger = ActiveSupport::Logger.new(@buffer)
end
def teardown
@@ -612,6 +615,12 @@ class FileStoreTest < ActiveSupport::TestCase
ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/)
end
end
+
+ def test_log_exception_when_cache_read_fails
+ File.expects(:exist?).raises(StandardError, "failed")
+ @cache.send(:read_entry, "winston", {})
+ assert_present @buffer.string
+ end
end
class MemoryStoreTest < ActiveSupport::TestCase
diff --git a/activesupport/test/clean_backtrace_test.rb b/activesupport/test/clean_backtrace_test.rb
index 32e346bb48..b14950acb3 100644
--- a/activesupport/test/clean_backtrace_test.rb
+++ b/activesupport/test/clean_backtrace_test.rb
@@ -6,8 +6,10 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase
@bc.add_filter { |line| line.gsub("/my/prefix", '') }
end
- test "backtrace should not contain prefix when it has been filtered out" do
- assert_equal "/my/class.rb", @bc.clean([ "/my/prefix/my/class.rb" ]).first
+ test "backtrace should filter all lines in a backtrace, removing prefixes" do
+ assert_equal \
+ ["/my/class.rb", "/my/module.rb"],
+ @bc.clean(["/my/prefix/my/class.rb", "/my/prefix/my/module.rb"])
end
test "backtrace cleaner should allow removing filters" do
@@ -19,11 +21,6 @@ class BacktraceCleanerFilterTest < ActiveSupport::TestCase
assert_equal "/my/other_prefix/my/class.rb", @bc.clean([ "/my/other_prefix/my/class.rb" ]).first
end
- test "backtrace should filter all lines in a backtrace" do
- assert_equal \
- ["/my/class.rb", "/my/module.rb"],
- @bc.clean([ "/my/prefix/my/class.rb", "/my/prefix/my/module.rb" ])
- end
end
class BacktraceCleanerSilencerTest < ActiveSupport::TestCase
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index 2e5ea2c360..da7729d066 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -5,7 +5,8 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
class Parent
include ActiveSupport::Configurable
config_accessor :foo
- config_accessor :bar, :instance_reader => false, :instance_writer => false
+ config_accessor :bar, instance_reader: false, instance_writer: false
+ config_accessor :baz, instance_accessor: false
end
class Child < Parent
@@ -19,13 +20,13 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
end
test "adds a configuration hash" do
- assert_equal({ :foo => :bar }, Parent.config)
+ assert_equal({ foo: :bar }, Parent.config)
end
test "adds a configuration hash to a module as well" do
mixin = Module.new { include ActiveSupport::Configurable }
mixin.config.foo = :bar
- assert_equal({ :foo => :bar }, mixin.config)
+ assert_equal({ foo: :bar }, mixin.config)
end
test "configuration hash is inheritable" do
@@ -39,8 +40,12 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
test "configuration accessors is not available on instance" do
instance = Parent.new
+
assert !instance.respond_to?(:bar)
assert !instance.respond_to?(:bar=)
+
+ assert !instance.respond_to?(:baz)
+ assert !instance.respond_to?(:baz=)
end
test "configuration hash is available on instance" do
@@ -71,6 +76,15 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
assert_method_defined child.new.config, :bar
end
+ test "should raise name error if attribute name is invalid" do
+ assert_raises NameError do
+ Class.new do
+ include ActiveSupport::Configurable
+ config_accessor "invalid attribute name"
+ end
+ end
+ end
+
def assert_method_defined(object, method)
methods = object.public_methods.map(&:to_s)
assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}"
@@ -80,4 +94,4 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
methods = object.public_methods.map(&:to_s)
assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}"
end
-end
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index e24a089650..a5987044b9 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -14,4 +14,9 @@ class BigDecimalTest < ActiveSupport::TestCase
bd = BigDecimal.new '10'
assert_equal bd, bd.to_d
end
+
+ def test_to_s
+ bd = BigDecimal.new '0.01'
+ assert_equal '0.01', bd.to_s
+ end
end
diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb
index e290a6e012..1c3ba8a7a0 100644
--- a/activesupport/test/core_ext/class/attribute_test.rb
+++ b/activesupport/test/core_ext/class/attribute_test.rb
@@ -66,6 +66,13 @@ class ClassAttributeTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { object.setting? }
end
+ test 'disabling both instance writer and reader' do
+ object = Class.new { class_attribute :setting, :instance_accessor => false }.new
+ assert_raise(NoMethodError) { object.setting }
+ assert_raise(NoMethodError) { object.setting? }
+ assert_raise(NoMethodError) { object.setting = 'boom' }
+ end
+
test 'works well with singleton classes' do
object = @klass.new
object.singleton_class.setting = 'foo'
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index e14a137f84..088b74a29a 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -289,6 +289,18 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month
end
+ def test_next_quarter_on_31st
+ assert_equal Date.new(2005, 11, 30), Date.new(2005, 8, 31).next_quarter
+ end
+
+ def test_prev_quarter_on_31st
+ assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).prev_quarter
+ end
+
+ def test_last_quarter_on_31st
+ assert_equal Date.new(2004, 2, 29), Date.new(2004, 5, 31).last_quarter
+ end
+
def test_yesterday_constructor
assert_equal Date.current - 1, Date.yesterday
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 3da0825489..183d58482d 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -271,6 +271,18 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month
end
+ def test_next_quarter_on_31st
+ assert_equal DateTime.civil(2005, 11, 30), DateTime.civil(2005, 8, 31).next_quarter
+ end
+
+ def test_prev_quarter_on_31st
+ assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).prev_quarter
+ end
+
+ def test_last_quarter_on_31st
+ assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 5, 31).last_quarter
+ end
+
def test_xmlschema
assert_match(/^1880-02-28T15:15:10\+00:?00$/, DateTime.civil(1880, 2, 28, 15, 15, 10).xmlschema)
assert_match(/^1980-02-28T15:15:10\+00:?00$/, DateTime.civil(1980, 2, 28, 15, 15, 10).xmlschema)
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 0bf48dd378..0a1abac767 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -84,6 +84,11 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal 10, (1..4.5).sum
assert_equal 6, (1...4).sum
assert_equal 'abc', ('a'..'c').sum
+ assert_equal 50_000_005_000_000, (0..10_000_000).sum
+ assert_equal 0, (10..0).sum
+ assert_equal 5, (10..0).sum(5)
+ assert_equal 10, (10..10).sum
+ assert_equal 42, (10...10).sum(42)
end
def test_index_by
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index 50c9c57aa6..128e956a8c 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -30,7 +30,7 @@ class AtomicWriteTest < ActiveSupport::TestCase
assert File.exist?(file_name)
end
assert File.exist?(file_name)
- assert_equal 0100755, file_mode
+ assert_equal 0100755 & ~File.umask, file_mode
assert_equal contents, File.read(file_name)
File.atomic_write(file_name, Dir.pwd) do |file|
@@ -38,7 +38,7 @@ class AtomicWriteTest < ActiveSupport::TestCase
assert File.exist?(file_name)
end
assert File.exist?(file_name)
- assert_equal 0100755, file_mode
+ assert_equal 0100755 & ~File.umask, file_mode
assert_equal contents, File.read(file_name)
ensure
File.unlink(file_name) rescue nil
@@ -51,7 +51,7 @@ class AtomicWriteTest < ActiveSupport::TestCase
assert !File.exist?(file_name)
end
assert File.exist?(file_name)
- assert_equal 0100666 ^ File.umask, file_mode
+ assert_equal 0100666 & ~File.umask, file_mode
assert_equal contents, File.read(file_name)
ensure
File.unlink(file_name) rescue nil
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 8239054117..4dc9f57038 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -4,6 +4,7 @@ require 'bigdecimal'
require 'active_support/core_ext/string/access'
require 'active_support/ordered_hash'
require 'active_support/core_ext/object/conversions'
+require 'active_support/core_ext/object/deep_dup'
require 'active_support/inflections'
class HashExtTest < ActiveSupport::TestCase
@@ -24,56 +25,207 @@ class HashExtTest < ActiveSupport::TestCase
def setup
@strings = { 'a' => 1, 'b' => 2 }
+ @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
@symbols = { :a => 1, :b => 2 }
+ @nested_symbols = { :a => { :b => { :c => 3 } } }
@mixed = { :a => 1, 'b' => 2 }
+ @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
@fixnums = { 0 => 1, 1 => 2 }
+ @nested_fixnums = { 0 => { 1 => { 2 => 3} } }
@illegal_symbols = { [] => 3 }
+ @nested_illegal_symbols = { [] => { [] => 3} }
+ @upcase_strings = { 'A' => 1, 'B' => 2 }
+ @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } }
end
def test_methods
h = {}
+ assert_respond_to h, :transform_keys
+ assert_respond_to h, :transform_keys!
+ assert_respond_to h, :deep_transform_keys
+ assert_respond_to h, :deep_transform_keys!
assert_respond_to h, :symbolize_keys
assert_respond_to h, :symbolize_keys!
+ assert_respond_to h, :deep_symbolize_keys
+ assert_respond_to h, :deep_symbolize_keys!
assert_respond_to h, :stringify_keys
assert_respond_to h, :stringify_keys!
+ assert_respond_to h, :deep_stringify_keys
+ assert_respond_to h, :deep_stringify_keys!
assert_respond_to h, :to_options
assert_respond_to h, :to_options!
end
+ def test_transform_keys
+ assert_equal @upcase_strings, @strings.transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @symbols.transform_keys{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @mixed.transform_keys{ |key| key.to_s.upcase }
+ end
+
+ def test_transform_keys_not_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.transform_keys{ |key| key.to_s.upcase }
+ assert_equal @mixed, transformed_hash
+ end
+
+ def test_deep_transform_keys
+ assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_mixed.deep_transform_keys{ |key| key.to_s.upcase }
+ end
+
+ def test_deep_transform_keys_not_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_transform_keys{ |key| key.to_s.upcase }
+ assert_equal @nested_mixed, transformed_hash
+ end
+
+ def test_transform_keys!
+ assert_equal @upcase_strings, @symbols.dup.transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @strings.dup.transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, @mixed.dup.transform_keys!{ |key| key.to_s.upcase }
+ end
+
+ def test_transform_keys_with_bang_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @upcase_strings, transformed_hash
+ assert_equal @mixed, { :a => 1, "b" => 2 }
+ end
+
+ def test_deep_transform_keys!
+ assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, @nested_mixed.deep_dup.deep_transform_keys!{ |key| key.to_s.upcase }
+ end
+
+ def test_deep_transform_keys_with_bang_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_transform_keys!{ |key| key.to_s.upcase }
+ assert_equal @nested_upcase_strings, transformed_hash
+ assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
+ end
+
def test_symbolize_keys
assert_equal @symbols, @symbols.symbolize_keys
assert_equal @symbols, @strings.symbolize_keys
assert_equal @symbols, @mixed.symbolize_keys
end
+ def test_symbolize_keys_not_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.symbolize_keys
+ assert_equal @mixed, transformed_hash
+ end
+
+ def test_deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_symbols.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_strings.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_mixed.deep_symbolize_keys
+ end
+
+ def test_deep_symbolize_keys_not_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_symbolize_keys
+ assert_equal @nested_mixed, transformed_hash
+ end
+
def test_symbolize_keys!
assert_equal @symbols, @symbols.dup.symbolize_keys!
assert_equal @symbols, @strings.dup.symbolize_keys!
assert_equal @symbols, @mixed.dup.symbolize_keys!
end
+ def test_symbolize_keys_with_bang_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.deep_symbolize_keys!
+ assert_equal @symbols, transformed_hash
+ assert_equal @mixed, { :a => 1, "b" => 2 }
+ end
+
+ def test_deep_symbolize_keys!
+ assert_equal @nested_symbols, @nested_symbols.deep_dup.deep_symbolize_keys!
+ assert_equal @nested_symbols, @nested_strings.deep_dup.deep_symbolize_keys!
+ assert_equal @nested_symbols, @nested_mixed.deep_dup.deep_symbolize_keys!
+ end
+
+ def test_deep_symbolize_keys_with_bang_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_symbolize_keys!
+ assert_equal @nested_symbols, transformed_hash
+ assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
+ end
+
def test_symbolize_keys_preserves_keys_that_cant_be_symbolized
assert_equal @illegal_symbols, @illegal_symbols.symbolize_keys
assert_equal @illegal_symbols, @illegal_symbols.dup.symbolize_keys!
end
+ def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_symbolize_keys
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys!
+ end
+
def test_symbolize_keys_preserves_fixnum_keys
assert_equal @fixnums, @fixnums.symbolize_keys
assert_equal @fixnums, @fixnums.dup.symbolize_keys!
end
+ def test_deep_symbolize_keys_preserves_fixnum_keys
+ assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys
+ assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys!
+ end
+
def test_stringify_keys
assert_equal @strings, @symbols.stringify_keys
assert_equal @strings, @strings.stringify_keys
assert_equal @strings, @mixed.stringify_keys
end
+ def test_stringify_keys_not_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.stringify_keys
+ assert_equal @mixed, transformed_hash
+ end
+
+ def test_deep_stringify_keys
+ assert_equal @nested_strings, @nested_symbols.deep_stringify_keys
+ assert_equal @nested_strings, @nested_strings.deep_stringify_keys
+ assert_equal @nested_strings, @nested_mixed.deep_stringify_keys
+ end
+
+ def test_deep_stringify_keys_not_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_stringify_keys
+ assert_equal @nested_mixed, transformed_hash
+ end
+
def test_stringify_keys!
assert_equal @strings, @symbols.dup.stringify_keys!
assert_equal @strings, @strings.dup.stringify_keys!
assert_equal @strings, @mixed.dup.stringify_keys!
end
+ def test_stringify_keys_with_bang_mutates
+ transformed_hash = @mixed.dup
+ transformed_hash.stringify_keys!
+ assert_equal @strings, transformed_hash
+ assert_equal @mixed, { :a => 1, "b" => 2 }
+ end
+
+ def test_deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.deep_dup.deep_stringify_keys!
+ end
+
+ def test_deep_stringify_keys_with_bang_mutates
+ transformed_hash = @nested_mixed.deep_dup
+ transformed_hash.deep_stringify_keys!
+ assert_equal @nested_strings, transformed_hash
+ assert_equal @nested_mixed, { 'a' => { :b => { 'c' => 3 } } }
+ end
+
def test_symbolize_keys_for_hash_with_indifferent_access
assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
@@ -81,22 +233,46 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
end
+ def test_deep_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
+ end
+
+
def test_symbolize_keys_bang_for_hash_with_indifferent_access
assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
end
+ def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
end
+ def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
end
+ def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
+ assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
def test_stringify_keys_for_hash_with_indifferent_access
assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
@@ -104,6 +280,13 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
end
+ def test_deep_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
+ end
+
def test_stringify_keys_bang_for_hash_with_indifferent_access
assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
@@ -111,6 +294,13 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
end
+ def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
+ end
+
def test_nested_under_indifferent_access
foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
@@ -267,6 +457,13 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal '1234', roundtrip.default
end
+ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
+ hash = HashWithIndifferentAccess.new {|h, k| h[k] = []}
+ hash[:a] << 1
+
+ assert_equal [1], hash[:a]
+ end
+
def test_indifferent_hash_with_array_of_hashes
hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access
assert_equal "1", hash[:urls][:url].first[:address]
@@ -297,6 +494,17 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal 1, h[:first]
end
+ def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.deep_stringify_keys
+ assert_equal 1, h['first']
+ h = HashWithIndifferentAccess.new
+ h['first'] = 1
+ h = h.deep_symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
def test_to_options_on_indifferent_preserves_hash
h = HashWithIndifferentAccess.new
h['first'] = 1
@@ -672,8 +880,8 @@ class HashToXmlTest < ActiveSupport::TestCase
:created_at => Time.utc(1999,2,2),
:local_created_at => Time.utc(1999,2,2).in_time_zone('Eastern Time (US & Canada)')
}.to_xml(@xml_options)
- assert_match %r{<created-at type=\"datetime\">1999-02-02T00:00:00Z</created-at>}, xml
- assert_match %r{<local-created-at type=\"datetime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
+ assert_match %r{<created-at type=\"dateTime\">1999-02-02T00:00:00Z</created-at>}, xml
+ assert_match %r{<local-created-at type=\"dateTime\">1999-02-01T19:00:00-05:00</local-created-at>}, xml
end
def test_multiple_records_from_xml_with_attributes_other_than_type_ignores_them_without_exploding
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index e90b9d454f..439bc87323 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -101,10 +101,10 @@ class KernelDebuggerTest < ActiveSupport::TestCase
@logger ||= MockStdErr.new
end
end
- Object.const_set("Rails", rails)
+ Object.const_set(:Rails, rails)
debugger
assert_match(/Debugger requested/, rails.logger.output.first)
ensure
- Object.send(:remove_const, "Rails")
+ Object.send(:remove_const, :Rails)
end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 6e1b3ca010..bd41311739 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -234,7 +234,7 @@ class ModuleTest < ActiveSupport::TestCase
def test_local_constant_names
ActiveSupport::Deprecation.silence do
- assert_equal %w(Constant1 Constant3), Ab.local_constant_names
+ assert_equal %w(Constant1 Constant3), Ab.local_constant_names.sort.map(&:to_s)
end
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 1cb1e25d4c..435f4aa5a1 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -186,3 +186,264 @@ class NumericExtSizeTest < ActiveSupport::TestCase
assert_equal 3458764513820540928, 3.exabyte
end
end
+
+class NumericExtFormattingTest < ActiveSupport::TestCase
+ def kilobytes(number)
+ number * 1024
+ end
+
+ def megabytes(number)
+ kilobytes(number) * 1024
+ end
+
+ def gigabytes(number)
+ megabytes(number) * 1024
+ end
+
+ def terabytes(number)
+ gigabytes(number) * 1024
+ end
+
+ def test_to_s__phone
+ assert_equal("555-1234", 5551234.to_s(:phone))
+ assert_equal("800-555-1212", 8005551212.to_s(:phone))
+ assert_equal("(800) 555-1212", 8005551212.to_s(:phone, :area_code => true))
+ assert_equal("800 555 1212", 8005551212.to_s(:phone, :delimiter => " "))
+ assert_equal("(800) 555-1212 x 123", 8005551212.to_s(:phone, :area_code => true, :extension => 123))
+ assert_equal("800-555-1212", 8005551212.to_s(:phone, :extension => " "))
+ assert_equal("555.1212", 5551212.to_s(:phone, :delimiter => '.'))
+ assert_equal("+1-800-555-1212", 8005551212.to_s(:phone, :country_code => 1))
+ assert_equal("+18005551212", 8005551212.to_s(:phone, :country_code => 1, :delimiter => ''))
+ assert_equal("22-555-1212", 225551212.to_s(:phone))
+ assert_equal("+45-22-555-1212", 225551212.to_s(:phone, :country_code => 45))
+ end
+
+ def test_to_s__currency
+ assert_equal("$1,234,567,890.50", 1234567890.50.to_s(:currency))
+ assert_equal("$1,234,567,890.51", 1234567890.506.to_s(:currency))
+ assert_equal("-$1,234,567,890.50", -1234567890.50.to_s(:currency))
+ assert_equal("-$ 1,234,567,890.50", -1234567890.50.to_s(:currency, :format => "%u %n"))
+ assert_equal("($1,234,567,890.50)", -1234567890.50.to_s(:currency, :negative_format => "(%u%n)"))
+ assert_equal("$1,234,567,892", 1234567891.50.to_s(:currency, :precision => 0))
+ assert_equal("$1,234,567,890.5", 1234567890.50.to_s(:currency, :precision => 1))
+ assert_equal("&pound;1234567890,50", 1234567890.50.to_s(:currency, :unit => "&pound;", :separator => ",", :delimiter => ""))
+ end
+
+
+ def test_to_s__rounded
+ assert_equal("-111.235", -111.2346.to_s(:rounded))
+ assert_equal("111.235", 111.2346.to_s(:rounded))
+ assert_equal("31.83", 31.825.to_s(:rounded, :precision => 2))
+ assert_equal("111.23", 111.2346.to_s(:rounded, :precision => 2))
+ assert_equal("111.00", 111.to_s(:rounded, :precision => 2))
+ assert_equal("3268", (32.6751 * 100.00).to_s(:rounded, :precision => 0))
+ assert_equal("112", 111.50.to_s(:rounded, :precision => 0))
+ assert_equal("1234567892", 1234567891.50.to_s(:rounded, :precision => 0))
+ assert_equal("0", 0.to_s(:rounded, :precision => 0))
+ assert_equal("0.00100", 0.001.to_s(:rounded, :precision => 5))
+ assert_equal("0.001", 0.00111.to_s(:rounded, :precision => 3))
+ assert_equal("10.00", 9.995.to_s(:rounded, :precision => 2))
+ assert_equal("11.00", 10.995.to_s(:rounded, :precision => 2))
+ assert_equal("0.00", -0.001.to_s(:rounded, :precision => 2))
+ end
+
+ def test_to_s__percentage
+ assert_equal("100.000%", 100.to_s(:percentage))
+ assert_equal("100%", 100.to_s(:percentage, :precision => 0))
+ assert_equal("302.06%", 302.0574.to_s(:percentage, :precision => 2))
+ assert_equal("123.4%", 123.400.to_s(:percentage, :precision => 3, :strip_insignificant_zeros => true))
+ assert_equal("1.000,000%", 1000.to_s(:percentage, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", 1000.to_s(:percentage, :format => "%n %"))
+ end
+
+ def test_to_s__delimited
+ assert_equal("12,345,678", 12345678.to_s(:delimited))
+ assert_equal("0", 0.to_s(:delimited))
+ assert_equal("123", 123.to_s(:delimited))
+ assert_equal("123,456", 123456.to_s(:delimited))
+ assert_equal("123,456.78", 123456.78.to_s(:delimited))
+ assert_equal("123,456.789", 123456.789.to_s(:delimited))
+ assert_equal("123,456.78901", 123456.78901.to_s(:delimited))
+ assert_equal("123,456,789.78901", 123456789.78901.to_s(:delimited))
+ assert_equal("0.78901", 0.78901.to_s(:delimited))
+ end
+
+ def test_to_s__delimited__with_options_hash
+ assert_equal '12 345 678', 12345678.to_s(:delimited, :delimiter => ' ')
+ assert_equal '12,345,678-05', 12345678.05.to_s(:delimited, :separator => '-')
+ assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :separator => ',', :delimiter => '.')
+ assert_equal '12.345.678,05', 12345678.05.to_s(:delimited, :delimiter => '.', :separator => ',')
+ end
+
+
+ def test_to_s__rounded_with_custom_delimiter_and_separator
+ assert_equal '31,83', 31.825.to_s(:rounded, :precision => 2, :separator => ',')
+ assert_equal '1.231,83', 1231.825.to_s(:rounded, :precision => 2, :separator => ',', :delimiter => '.')
+ end
+
+ def test_to_s__rounded__with_significant_digits
+ assert_equal "124000", 123987.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "120000000", 123987876.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "9775", 9775.to_s(:rounded, :precision => 4, :significant => true )
+ assert_equal "5.4", 5.3923.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "5", 5.3923.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "1", 1.232.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "7", 7.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "1", 1.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "53", 52.7923.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "9775.00", 9775.to_s(:rounded, :precision => 6, :significant => true )
+ assert_equal "5.392900", 5.3929.to_s(:rounded, :precision => 7, :significant => true )
+ assert_equal "0.0", 0.to_s(:rounded, :precision => 2, :significant => true )
+ assert_equal "0", 0.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "0.0001", 0.0001.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "0.000100", 0.0001.to_s(:rounded, :precision => 3, :significant => true )
+ assert_equal "0.0001", 0.0001111.to_s(:rounded, :precision => 1, :significant => true )
+ assert_equal "10.0", 9.995.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "9.99", 9.994.to_s(:rounded, :precision => 3, :significant => true)
+ assert_equal "11.0", 10.995.to_s(:rounded, :precision => 3, :significant => true)
+ end
+
+ def test_to_s__rounded__with_strip_insignificant_zeros
+ assert_equal "9775.43", 9775.43.to_s(:rounded, :precision => 4, :strip_insignificant_zeros => true )
+ assert_equal "9775.2", 9775.2.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ assert_equal "0", 0.to_s(:rounded, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ end
+
+ def test_to_s__rounded__with_significant_true_and_zero_precision
+ # Zero precision with significant is a mistake (would always return zero),
+ # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
+ assert_equal "124", 123.987.to_s(:rounded, :precision => 0, :significant => true)
+ assert_equal "12", 12.to_s(:rounded, :precision => 0, :significant => true )
+ end
+
+ def test_to_s__human_size
+ assert_equal '0 Bytes', 0.to_s(:human_size)
+ assert_equal '1 Byte', 1.to_s(:human_size)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size)
+ assert_equal '123 Bytes', 123.to_s(:human_size)
+ assert_equal '1.21 KB', 1234.to_s(:human_size)
+ assert_equal '12.1 KB', 12345.to_s(:human_size)
+ assert_equal '1.18 MB', 1234567.to_s(:human_size)
+ assert_equal '1.15 GB', 1234567890.to_s(:human_size)
+ assert_equal '1.12 TB', 1234567890123.to_s(:human_size)
+ assert_equal '1030 TB', terabytes(1026).to_s(:human_size)
+ assert_equal '444 KB', kilobytes(444).to_s(:human_size)
+ assert_equal '1020 MB', megabytes(1023).to_s(:human_size)
+ assert_equal '3 TB', terabytes(3).to_s(:human_size)
+ assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2)
+ assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4)
+ assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4)
+ assert_equal '1 Byte', 1.1.to_s(:human_size)
+ assert_equal '10 Bytes', 10.to_s(:human_size)
+ end
+
+ def test_to_s__human_size_with_si_prefix
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si)
+ assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ end
+
+ def test_to_s__human_size_with_options_hash
+ assert_equal '1.2 MB', 1234567.to_s(:human_size, :precision => 2)
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :precision => 4)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2)
+ assert_equal '1.01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4)
+ assert_equal '10 KB', kilobytes(10.000).to_s(:human_size, :precision => 4)
+ assert_equal '1 TB', 1234567890123.to_s(:human_size, :precision => 1)
+ assert_equal '500 MB', 524288000.to_s(:human_size, :precision=>3)
+ assert_equal '10 MB', 9961472.to_s(:human_size, :precision=>0)
+ assert_equal '40 KB', 41010.to_s(:human_size, :precision => 1)
+ assert_equal '40 KB', 41100.to_s(:human_size, :precision => 2)
+ assert_equal '1.0 KB', kilobytes(1.0123).to_s(:human_size, :precision => 2, :strip_insignificant_zeros => false)
+ assert_equal '1.012 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :significant => false)
+ assert_equal '1 KB', kilobytes(1.0123).to_s(:human_size, :precision => 0, :significant => true) #ignores significant it precision is 0
+ end
+
+ def test_to_s__human_size_with_custom_delimiter_and_separator
+ assert_equal '1,01 KB', kilobytes(1.0123).to_s(:human_size, :precision => 3, :separator => ',')
+ assert_equal '1,01 KB', kilobytes(1.0100).to_s(:human_size, :precision => 4, :separator => ',')
+ assert_equal '1.000,1 TB', terabytes(1000.1).to_s(:human_size, :precision => 5, :delimiter => '.', :separator => ',')
+ end
+
+ def test_number_to_human
+ assert_equal '-123', -123.to_s(:human)
+ assert_equal '-0.5', -0.5.to_s(:human)
+ assert_equal '0', 0.to_s(:human)
+ assert_equal '0.5', 0.5.to_s(:human)
+ assert_equal '123', 123.to_s(:human)
+ assert_equal '1.23 Thousand', 1234.to_s(:human)
+ assert_equal '12.3 Thousand', 12345.to_s(:human)
+ assert_equal '1.23 Million', 1234567.to_s(:human)
+ assert_equal '1.23 Billion', 1234567890.to_s(:human)
+ assert_equal '1.23 Trillion', 1234567890123.to_s(:human)
+ assert_equal '1.23 Quadrillion', 1234567890123456.to_s(:human)
+ assert_equal '1230 Quadrillion', 1234567890123456789.to_s(:human)
+ assert_equal '490 Thousand', 489939.to_s(:human, :precision => 2)
+ assert_equal '489.9 Thousand', 489939.to_s(:human, :precision => 4)
+ assert_equal '489 Thousand', 489000.to_s(:human, :precision => 4)
+ assert_equal '489.0 Thousand', 489000.to_s(:human, :precision => 4, :strip_insignificant_zeros => false)
+ assert_equal '1.2346 Million', 1234567.to_s(:human, :precision => 4, :significant => false)
+ assert_equal '1,2 Million', 1234567.to_s(:human, :precision => 1, :significant => false, :separator => ',')
+ assert_equal '1 Million', 1234567.to_s(:human, :precision => 0, :significant => true, :separator => ',') #significant forced to false
+ end
+
+ def test_number_to_human_with_custom_units
+ #Only integers
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123 lt', 123456.to_s(:human, :units => volume)
+ assert_equal '12 ml', 12.to_s(:human, :units => volume)
+ assert_equal '1.23 m3', 1234567.to_s(:human, :units => volume)
+
+ #Including fractionals
+ distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal '1.23 mm', 0.00123.to_s(:human, :units => distance)
+ assert_equal '1.23 cm', 0.0123.to_s(:human, :units => distance)
+ assert_equal '1.23 dm', 0.123.to_s(:human, :units => distance)
+ assert_equal '1.23 m', 1.23.to_s(:human, :units => distance)
+ assert_equal '1.23 dam', 12.3.to_s(:human, :units => distance)
+ assert_equal '1.23 hm', 123.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '1.23 km', 1230.to_s(:human, :units => distance)
+ assert_equal '12.3 km', 12300.to_s(:human, :units => distance)
+
+ #The quantifiers don't need to be a continuous sequence
+ gangster = {:hundred => "hundred bucks", :million => "thousand quids"}
+ assert_equal '1 hundred bucks', 100.to_s(:human, :units => gangster)
+ assert_equal '25 hundred bucks', 2500.to_s(:human, :units => gangster)
+ assert_equal '25 thousand quids', 25000000.to_s(:human, :units => gangster)
+ assert_equal '12300 thousand quids', 12345000000.to_s(:human, :units => gangster)
+
+ #Spaces are stripped from the resulting string
+ assert_equal '4', 4.to_s(:human, :units => {:unit => "", :ten => 'tens '})
+ assert_equal '4.5 tens', 45.to_s(:human, :units => {:unit => "", :ten => ' tens '})
+ end
+
+ def test_number_to_human_with_custom_format
+ assert_equal '123 times Thousand', 123456.to_s(:human, :format => "%n times %u")
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123.lt', 123456.to_s(:human, :units => volume, :format => "%n.%u")
+ end
+
+ def test_to_s__injected_on_proper_types
+ assert_equal Fixnum, 1230.class
+ assert_equal '1.23 Thousand', 1230.to_s(:human)
+
+ assert_equal Float, Float(1230).class
+ assert_equal '1.23 Thousand', Float(1230).to_s(:human)
+
+ assert_equal Bignum, (100**10).class
+ assert_equal '100000 Quadrillion', (100**10).to_s(:human)
+
+ assert_equal BigDecimal, BigDecimal("1000010").class
+ assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
+ end
+end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 8437ef1347..e5b774425e 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -290,6 +290,10 @@ class StringInflectionsTest < ActiveSupport::TestCase
"\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10)
end
+ def test_truncate_should_not_be_html_safe
+ assert !"Hello World!".truncate(12).html_safe?
+ end
+
def test_constantize
run_constantize_tests_on do |string|
string.constantize
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 15c04bedf7..d6f285598e 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -906,4 +906,17 @@ class TimeExtMarshalingTest < ActiveSupport::TestCase
assert_equal t.to_f, unmarshaled.to_f
assert_equal t, unmarshaled
end
+
+
+ def test_next_quarter_on_31st
+ assert_equal Time.local(2005, 11, 30), Time.local(2005, 8, 31).next_quarter
+ end
+
+ def test_prev_quarter_on_31st
+ assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).prev_quarter
+ end
+
+ def test_last_quarter_on_31st
+ assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter
+ end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 081e6a16fd..69829bcda5 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -39,6 +39,19 @@ class DependenciesTest < ActiveSupport::TestCase
with_loading 'autoloading_fixtures', &block
end
+ def test_depend_on_path
+ skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0'
+
+ expected = assert_raises(LoadError) do
+ Kernel.require 'omgwtfbbq'
+ end
+
+ e = assert_raises(LoadError) do
+ ActiveSupport::Dependencies.depend_on 'omgwtfbbq'
+ end
+ assert_equal expected.path, e.path
+ end
+
def test_tracking_loaded_files
require_dependency 'dependencies/service_one'
require_dependency 'dependencies/service_two'
@@ -60,10 +73,6 @@ class DependenciesTest < ActiveSupport::TestCase
assert_raise(MissingSourceFile) { require_dependency("missing_service") }
end
- def test_missing_association_raises_nothing
- assert_nothing_raised { require_association("missing_model") }
- end
-
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
with_loading do
filename = 'dependencies/raises_exception'
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 91ae6bc189..1f32e4ff92 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -413,6 +413,16 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ Irregularities.each do |irregularity|
+ singular, plural = *irregularity
+ ActiveSupport::Inflector.inflections do |inflect|
+ define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do
+ inflect.irregular(singular, plural)
+ assert_equal singular, ActiveSupport::Inflector.singularize(singular)
+ end
+ end
+ end
+
[ :all, [] ].each do |scope|
ActiveSupport::Inflector.inflections do |inflect|
define_method("test_clear_inflections_with_#{scope.kind_of?(Array) ? "no_arguments" : scope}") do
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 9fa1f417e4..ca4efd2e59 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -308,5 +308,7 @@ module InflectorTestCases
'child' => 'children',
'sex' => 'sexes',
'move' => 'moves',
+ 'cow' => 'kine',
+ 'zombie' => 'zombies',
}
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 0566ebf291..a947635f4a 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -54,8 +54,6 @@ class TestJSONEncoding < ActiveSupport::TestCase
HashlikeTests = [[ Hashlike.new, %({\"a\":1}) ]]
CustomTests = [[ Custom.new, '"custom"' ]]
- VariableTests = [[ ActiveSupport::JSON::Variable.new('foo'), 'foo'],
- [ ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")']]
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
@@ -285,6 +283,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
end
+ def test_nil_true_and_false_represented_as_themselves
+ assert_equal nil, nil.as_json
+ assert_equal true, true.as_json
+ assert_equal false, false.as_json
+ end
+
protected
def object_keys(json_object)
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index fc9fa90d07..bcb393c7bc 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -221,13 +221,15 @@ module Notifications
assert_equal Hash[:payload => :bar], event.payload
end
- def test_event_is_parent_based_on_time_frame
+ def test_event_is_parent_based_on_children
time = Time.utc(2009, 01, 01, 0, 0, 1)
parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, random_id, {})
child = event(:foo, time, time + 10, random_id, {})
not_child = event(:foo, time, time + 100, random_id, {})
+ parent.children << child
+
assert parent.parent_of?(child)
assert !child.parent_of?(parent)
assert !parent.parent_of?(not_child)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
new file mode 100644
index 0000000000..9b7d7f020c
--- /dev/null
+++ b/activesupport/test/number_helper_test.rb
@@ -0,0 +1,375 @@
+require 'abstract_unit'
+require 'active_support/number_helper'
+
+module ActiveSupport
+ module NumberHelper
+ class NumberHelperTest < ActiveSupport::TestCase
+
+ class TestClassWithInstanceNumberHelpers
+ include ActiveSupport::NumberHelper
+ end
+
+ class TestClassWithClassNumberHelpers
+ extend ActiveSupport::NumberHelper
+ end
+
+ def setup
+ @instance_with_helpers = TestClassWithInstanceNumberHelpers.new
+ end
+
+ def kilobytes(number)
+ number * 1024
+ end
+
+ def megabytes(number)
+ kilobytes(number) * 1024
+ end
+
+ def gigabytes(number)
+ megabytes(number) * 1024
+ end
+
+ def terabytes(number)
+ gigabytes(number) * 1024
+ end
+
+ def test_number_to_phone
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("555-1234", number_helper.number_to_phone(5551234))
+ assert_equal("800-555-1212", number_helper.number_to_phone(8005551212))
+ assert_equal("(800) 555-1212", number_helper.number_to_phone(8005551212, {:area_code => true}))
+ assert_equal("", number_helper.number_to_phone("", {:area_code => true}))
+ assert_equal("800 555 1212", number_helper.number_to_phone(8005551212, {:delimiter => " "}))
+ assert_equal("(800) 555-1212 x 123", number_helper.number_to_phone(8005551212, {:area_code => true, :extension => 123}))
+ assert_equal("800-555-1212", number_helper.number_to_phone(8005551212, :extension => " "))
+ assert_equal("555.1212", number_helper.number_to_phone(5551212, :delimiter => '.'))
+ assert_equal("800-555-1212", number_helper.number_to_phone("8005551212"))
+ assert_equal("+1-800-555-1212", number_helper.number_to_phone(8005551212, :country_code => 1))
+ assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
+ assert_equal("22-555-1212", number_helper.number_to_phone(225551212))
+ assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45))
+ end
+ end
+
+ def test_number_to_currency
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("$1,234,567,890.50", number_helper.number_to_currency(1234567890.50))
+ assert_equal("$1,234,567,890.51", number_helper.number_to_currency(1234567890.506))
+ assert_equal("-$1,234,567,890.50", number_helper.number_to_currency(-1234567890.50))
+ assert_equal("-$ 1,234,567,890.50", number_helper.number_to_currency(-1234567890.50, {:format => "%u %n"}))
+ assert_equal("($1,234,567,890.50)", number_helper.number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"}))
+ assert_equal("$1,234,567,892", number_helper.number_to_currency(1234567891.50, {:precision => 0}))
+ assert_equal("$1,234,567,890.5", number_helper.number_to_currency(1234567890.50, {:precision => 1}))
+ assert_equal("&pound;1234567890,50", number_helper.number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
+ assert_equal("$1,234,567,890.50", number_helper.number_to_currency("1234567890.50"))
+ assert_equal("1,234,567,890.50 K&#269;", number_helper.number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
+ assert_equal("1,234,567,890.50 - K&#269;", number_helper.number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
+ assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"}))
+ assert_equal("(0.00)", number_helper.number_to_currency(-0.0, {:unit => "", :negative_format => "(%n)"}))
+ end
+ end
+
+ def test_number_to_percentage
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("100.000%", number_helper.number_to_percentage(100))
+ assert_equal("100%", number_helper.number_to_percentage(100, {:precision => 0}))
+ assert_equal("302.06%", number_helper.number_to_percentage(302.0574, {:precision => 2}))
+ assert_equal("100.000%", number_helper.number_to_percentage("100"))
+ assert_equal("1000.000%", number_helper.number_to_percentage("1000"))
+ assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
+ assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %"))
+ end
+ end
+
+ def test_to_delimited
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("12,345,678", number_helper.number_to_delimited(12345678))
+ assert_equal("0", number_helper.number_to_delimited(0))
+ assert_equal("123", number_helper.number_to_delimited(123))
+ assert_equal("123,456", number_helper.number_to_delimited(123456))
+ assert_equal("123,456.78", number_helper.number_to_delimited(123456.78))
+ assert_equal("123,456.789", number_helper.number_to_delimited(123456.789))
+ assert_equal("123,456.78901", number_helper.number_to_delimited(123456.78901))
+ assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901))
+ assert_equal("0.78901", number_helper.number_to_delimited(0.78901))
+ assert_equal("123,456.78", number_helper.number_to_delimited("123456.78"))
+ end
+ end
+
+ def test_to_delimited_with_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '12 345 678', number_helper.number_to_delimited(12345678, :delimiter => ' ')
+ assert_equal '12,345,678-05', number_helper.number_to_delimited(12345678.05, :separator => '-')
+ assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :separator => ',', :delimiter => '.')
+ assert_equal '12.345.678,05', number_helper.number_to_delimited(12345678.05, :delimiter => '.', :separator => ',')
+ end
+ end
+
+ def test_to_rounded
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("-111.235", number_helper.number_to_rounded(-111.2346))
+ assert_equal("111.235", number_helper.number_to_rounded(111.2346))
+ assert_equal("31.83", number_helper.number_to_rounded(31.825, :precision => 2))
+ assert_equal("111.23", number_helper.number_to_rounded(111.2346, :precision => 2))
+ assert_equal("111.00", number_helper.number_to_rounded(111, :precision => 2))
+ assert_equal("111.235", number_helper.number_to_rounded("111.2346"))
+ assert_equal("31.83", number_helper.number_to_rounded("31.825", :precision => 2))
+ assert_equal("3268", number_helper.number_to_rounded((32.6751 * 100.00), :precision => 0))
+ assert_equal("112", number_helper.number_to_rounded(111.50, :precision => 0))
+ assert_equal("1234567892", number_helper.number_to_rounded(1234567891.50, :precision => 0))
+ assert_equal("0", number_helper.number_to_rounded(0, :precision => 0))
+ assert_equal("0.00100", number_helper.number_to_rounded(0.001, :precision => 5))
+ assert_equal("0.001", number_helper.number_to_rounded(0.00111, :precision => 3))
+ assert_equal("10.00", number_helper.number_to_rounded(9.995, :precision => 2))
+ assert_equal("11.00", number_helper.number_to_rounded(10.995, :precision => 2))
+ assert_equal("0.00", number_helper.number_to_rounded(-0.001, :precision => 2))
+ end
+ end
+
+ def test_to_rounded_with_custom_delimiter_and_separator
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '31,83', number_helper.number_to_rounded(31.825, :precision => 2, :separator => ',')
+ assert_equal '1.231,83', number_helper.number_to_rounded(1231.825, :precision => 2, :separator => ',', :delimiter => '.')
+ end
+ end
+
+ def test_to_rounded_with_significant_digits
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal "124000", number_helper.number_to_rounded(123987, :precision => 3, :significant => true)
+ assert_equal "120000000", number_helper.number_to_rounded(123987876, :precision => 2, :significant => true )
+ assert_equal "40000", number_helper.number_to_rounded("43523", :precision => 1, :significant => true )
+ assert_equal "9775", number_helper.number_to_rounded(9775, :precision => 4, :significant => true )
+ assert_equal "5.4", number_helper.number_to_rounded(5.3923, :precision => 2, :significant => true )
+ assert_equal "5", number_helper.number_to_rounded(5.3923, :precision => 1, :significant => true )
+ assert_equal "1", number_helper.number_to_rounded(1.232, :precision => 1, :significant => true )
+ assert_equal "7", number_helper.number_to_rounded(7, :precision => 1, :significant => true )
+ assert_equal "1", number_helper.number_to_rounded(1, :precision => 1, :significant => true )
+ assert_equal "53", number_helper.number_to_rounded(52.7923, :precision => 2, :significant => true )
+ assert_equal "9775.00", number_helper.number_to_rounded(9775, :precision => 6, :significant => true )
+ assert_equal "5.392900", number_helper.number_to_rounded(5.3929, :precision => 7, :significant => true )
+ assert_equal "0.0", number_helper.number_to_rounded(0, :precision => 2, :significant => true )
+ assert_equal "0", number_helper.number_to_rounded(0, :precision => 1, :significant => true )
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001, :precision => 1, :significant => true )
+ assert_equal "0.000100", number_helper.number_to_rounded(0.0001, :precision => 3, :significant => true )
+ assert_equal "0.0001", number_helper.number_to_rounded(0.0001111, :precision => 1, :significant => true )
+ assert_equal "10.0", number_helper.number_to_rounded(9.995, :precision => 3, :significant => true)
+ assert_equal "9.99", number_helper.number_to_rounded(9.994, :precision => 3, :significant => true)
+ assert_equal "11.0", number_helper.number_to_rounded(10.995, :precision => 3, :significant => true)
+ end
+ end
+
+ def test_to_rounded_with_strip_insignificant_zeros
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal "9775.43", number_helper.number_to_rounded(9775.43, :precision => 4, :strip_insignificant_zeros => true )
+ assert_equal "9775.2", number_helper.number_to_rounded(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ assert_equal "0", number_helper.number_to_rounded(0, :precision => 6, :significant => true, :strip_insignificant_zeros => true )
+ end
+ end
+
+ def test_to_rounded_with_significant_true_and_zero_precision
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ # Zero precision with significant is a mistake (would always return zero),
+ # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size)
+ assert_equal "124", number_helper.number_to_rounded(123.987, :precision => 0, :significant => true)
+ assert_equal "12", number_helper.number_to_rounded(12, :precision => 0, :significant => true )
+ assert_equal "12", number_helper.number_to_rounded("12.3", :precision => 0, :significant => true )
+ end
+ end
+
+ def test_number_number_to_human_size
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '0 Bytes', number_helper.number_to_human_size(0)
+ assert_equal '1 Byte', number_helper.number_to_human_size(1)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123)
+ assert_equal '1.21 KB', number_helper.number_to_human_size(1234)
+ assert_equal '12.1 KB', number_helper.number_to_human_size(12345)
+ assert_equal '1.18 MB', number_helper.number_to_human_size(1234567)
+ assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890)
+ assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123)
+ assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026))
+ assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444))
+ assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023))
+ assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3))
+ assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4)
+ assert_equal '123 Bytes', number_helper.number_to_human_size('123')
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2)
+ assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4)
+ assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4)
+ assert_equal '1 Byte', number_helper.number_to_human_size(1.1)
+ assert_equal '10 Bytes', number_helper.number_to_human_size(10)
+ end
+ end
+
+ def test_number_to_human_size_with_si_prefix
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si)
+ assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si)
+ assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si)
+ assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
+ assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
+ assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ end
+ end
+
+ def test_number_to_human_size_with_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '1.2 MB', number_helper.number_to_human_size(1234567, :precision => 2)
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :precision => 4)
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2)
+ assert_equal '1.01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4)
+ assert_equal '10 KB', number_helper.number_to_human_size(kilobytes(10.000), :precision => 4)
+ assert_equal '1 TB', number_helper.number_to_human_size(1234567890123, :precision => 1)
+ assert_equal '500 MB', number_helper.number_to_human_size(524288000, :precision=>3)
+ assert_equal '10 MB', number_helper.number_to_human_size(9961472, :precision=>0)
+ assert_equal '40 KB', number_helper.number_to_human_size(41010, :precision => 1)
+ assert_equal '40 KB', number_helper.number_to_human_size(41100, :precision => 2)
+ assert_equal '1.0 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false)
+ assert_equal '1.012 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false)
+ assert_equal '1 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0
+ end
+ end
+
+ def test_number_to_human_size_with_custom_delimiter_and_separator
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',')
+ assert_equal '1,01 KB', number_helper.number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',')
+ assert_equal '1.000,1 TB', number_helper.number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',')
+ end
+ end
+
+ def test_number_to_human
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '-123', number_helper.number_to_human(-123)
+ assert_equal '-0.5', number_helper.number_to_human(-0.5)
+ assert_equal '0', number_helper.number_to_human(0)
+ assert_equal '0.5', number_helper.number_to_human(0.5)
+ assert_equal '123', number_helper.number_to_human(123)
+ assert_equal '1.23 Thousand', number_helper.number_to_human(1234)
+ assert_equal '12.3 Thousand', number_helper.number_to_human(12345)
+ assert_equal '1.23 Million', number_helper.number_to_human(1234567)
+ assert_equal '1.23 Billion', number_helper.number_to_human(1234567890)
+ assert_equal '1.23 Trillion', number_helper.number_to_human(1234567890123)
+ assert_equal '1.23 Quadrillion', number_helper.number_to_human(1234567890123456)
+ assert_equal '1230 Quadrillion', number_helper.number_to_human(1234567890123456789)
+ assert_equal '490 Thousand', number_helper.number_to_human(489939, :precision => 2)
+ assert_equal '489.9 Thousand', number_helper.number_to_human(489939, :precision => 4)
+ assert_equal '489 Thousand', number_helper.number_to_human(489000, :precision => 4)
+ assert_equal '489.0 Thousand', number_helper.number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false)
+ assert_equal '1.2346 Million', number_helper.number_to_human(1234567, :precision => 4, :significant => false)
+ assert_equal '1,2 Million', number_helper.number_to_human(1234567, :precision => 1, :significant => false, :separator => ',')
+ assert_equal '1 Million', number_helper.number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false
+ end
+ end
+
+ def test_number_to_human_with_custom_units
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ #Only integers
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123 lt', number_helper.number_to_human(123456, :units => volume)
+ assert_equal '12 ml', number_helper.number_to_human(12, :units => volume)
+ assert_equal '1.23 m3', number_helper.number_to_human(1234567, :units => volume)
+
+ #Including fractionals
+ distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"}
+ assert_equal '1.23 mm', number_helper.number_to_human(0.00123, :units => distance)
+ assert_equal '1.23 cm', number_helper.number_to_human(0.0123, :units => distance)
+ assert_equal '1.23 dm', number_helper.number_to_human(0.123, :units => distance)
+ assert_equal '1.23 m', number_helper.number_to_human(1.23, :units => distance)
+ assert_equal '1.23 dam', number_helper.number_to_human(12.3, :units => distance)
+ assert_equal '1.23 hm', number_helper.number_to_human(123, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '1.23 km', number_helper.number_to_human(1230, :units => distance)
+ assert_equal '12.3 km', number_helper.number_to_human(12300, :units => distance)
+
+ #The quantifiers don't need to be a continuous sequence
+ gangster = {:hundred => "hundred bucks", :million => "thousand quids"}
+ assert_equal '1 hundred bucks', number_helper.number_to_human(100, :units => gangster)
+ assert_equal '25 hundred bucks', number_helper.number_to_human(2500, :units => gangster)
+ assert_equal '25 thousand quids', number_helper.number_to_human(25000000, :units => gangster)
+ assert_equal '12300 thousand quids', number_helper.number_to_human(12345000000, :units => gangster)
+
+ #Spaces are stripped from the resulting string
+ assert_equal '4', number_helper.number_to_human(4, :units => {:unit => "", :ten => 'tens '})
+ assert_equal '4.5 tens', number_helper.number_to_human(45, :units => {:unit => "", :ten => ' tens '})
+ end
+ end
+
+ def test_number_to_human_with_custom_format
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '123 times Thousand', number_helper.number_to_human(123456, :format => "%n times %u")
+ volume = {:unit => "ml", :thousand => "lt", :million => "m3"}
+ assert_equal '123.lt', number_helper.number_to_human(123456, :units => volume, :format => "%n.%u")
+ end
+ end
+
+ def test_number_helpers_should_return_nil_when_given_nil
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_nil number_helper.number_to_phone(nil)
+ assert_nil number_helper.number_to_currency(nil)
+ assert_nil number_helper.number_to_percentage(nil)
+ assert_nil number_helper.number_to_delimited(nil)
+ assert_nil number_helper.number_to_rounded(nil)
+ assert_nil number_helper.number_to_human_size(nil)
+ assert_nil number_helper.number_to_human(nil)
+ end
+ end
+
+ def test_number_helpers_do_not_mutate_options_hash
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ options = { 'raise' => true }
+
+ number_helper.number_to_phone(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_currency(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_percentage(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_delimited(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_rounded(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_human_size(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_helper.number_to_human(1, options)
+ assert_equal({ 'raise' => true }, options)
+ end
+ end
+
+ def test_number_helpers_should_return_non_numeric_param_unchanged
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal("+1-x x 123", number_helper.number_to_phone("x", :country_code => 1, :extension => 123))
+ assert_equal("x", number_helper.number_to_phone("x"))
+ assert_equal("$x.", number_helper.number_to_currency("x."))
+ assert_equal("$x", number_helper.number_to_currency("x"))
+ assert_equal("x%", number_helper.number_to_percentage("x"))
+ assert_equal("x", number_helper.number_to_delimited("x"))
+ assert_equal("x.", number_helper.number_to_rounded("x."))
+ assert_equal("x", number_helper.number_to_rounded("x"))
+ assert_equal "x", number_helper.number_to_human_size('x')
+ assert_equal "x", number_helper.number_to_human('x')
+ end
+ end
+
+ def test_extending_or_including_number_helper_correctly_hides_private_methods
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert !number_helper.respond_to?(:valid_float?)
+ assert number_helper.respond_to?(:valid_float?, true)
+ end
+ end
+
+ end
+ end
+end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index e5b5547478..c02bfa8497 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -18,11 +18,9 @@ module ActiveSupport
end
end
- def test_callback_with_exception
+ def test_standard_error_raised_within_setup_callback_is_puked
tc = Class.new(TestCase) do
- def self.name
- nil
- end
+ def self.name; nil; end
setup :bad_callback
def bad_callback; raise 'oh noes' end
@@ -41,11 +39,9 @@ module ActiveSupport
assert_equal 'oh noes', exception.message
end
- def test_teardown_callback_with_exception
+ def test_standard_error_raised_within_teardown_callback_is_puked
tc = Class.new(TestCase) do
- def self.name
- nil
- end
+ def self.name; nil; end
teardown :bad_callback
def bad_callback; raise 'oh noes' end
@@ -63,5 +59,51 @@ module ActiveSupport
assert_equal test_name, name
assert_equal 'oh noes', exception.message
end
+
+ def test_passthrough_exception_raised_within_test_method_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ def test_which_raises_interrupt; raise Interrupt; end
+ end
+
+ test_name = 'test_which_raises_interrupt'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
+
+ def test_passthrough_exception_raised_within_setup_callback_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ setup :callback_which_raises_interrupt
+ def callback_which_raises_interrupt; raise Interrupt; end
+ def test_true; assert true end
+ end
+
+ test_name = 'test_true'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
+
+ def test_passthrough_exception_raised_within_teardown_callback_is_not_rescued
+ tc = Class.new(TestCase) do
+ def self.name; nil; end
+
+ teardown :callback_which_raises_interrupt
+ def callback_which_raises_interrupt; raise Interrupt; end
+ def test_true; assert true end
+ end
+
+ test_name = 'test_true'
+ fr = FakeRunner.new
+
+ test = tc.new test_name
+ assert_raises(Interrupt) { test.run fr }
+ end
end
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 11506554a9..2473cec384 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -15,68 +15,64 @@ class AssertDifferenceTest < ActiveSupport::TestCase
@object.num = 0
end
- if lambda { }.respond_to?(:binding)
- def test_assert_no_difference
- assert_no_difference '@object.num' do
- # ...
- end
+ def test_assert_no_difference
+ assert_no_difference '@object.num' do
+ # ...
end
+ end
- def test_assert_difference
- assert_difference '@object.num', +1 do
- @object.increment
- end
+ def test_assert_difference
+ assert_difference '@object.num', +1 do
+ @object.increment
end
+ end
- def test_assert_difference_with_implicit_difference
- assert_difference '@object.num' do
- @object.increment
- end
+ def test_assert_difference_with_implicit_difference
+ assert_difference '@object.num' do
+ @object.increment
end
+ end
- def test_arbitrary_expression
- assert_difference '@object.num + 1', +2 do
- @object.increment
- @object.increment
- end
+ def test_arbitrary_expression
+ assert_difference '@object.num + 1', +2 do
+ @object.increment
+ @object.increment
end
+ end
- def test_negative_differences
- assert_difference '@object.num', -1 do
- @object.decrement
- end
+ def test_negative_differences
+ assert_difference '@object.num', -1 do
+ @object.decrement
end
+ end
- def test_expression_is_evaluated_in_the_appropriate_scope
- silence_warnings do
- local_scope = local_scope = 'foo'
- assert_difference('local_scope; @object.num') { @object.increment }
- end
+ def test_expression_is_evaluated_in_the_appropriate_scope
+ silence_warnings do
+ local_scope = local_scope = 'foo'
+ assert_difference('local_scope; @object.num') { @object.increment }
end
+ end
- def test_array_of_expressions
- assert_difference [ '@object.num', '@object.num + 1' ], +1 do
- @object.increment
- end
+ def test_array_of_expressions
+ assert_difference [ '@object.num', '@object.num + 1' ], +1 do
+ @object.increment
end
+ end
- def test_array_of_expressions_identify_failure
- assert_raises(MiniTest::Assertion) do
- assert_difference ['@object.num', '1 + 1'] do
- @object.increment
- end
+ def test_array_of_expressions_identify_failure
+ assert_raises(MiniTest::Assertion) do
+ assert_difference ['@object.num', '1 + 1'] do
+ @object.increment
end
end
+ end
- def test_array_of_expressions_identify_failure_when_message_provided
- assert_raises(MiniTest::Assertion) do
- assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
- @object.increment
- end
+ def test_array_of_expressions_identify_failure_when_message_provided
+ assert_raises(MiniTest::Assertion) do
+ assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
+ @object.increment
end
end
- else
- def default_test; end
end
end
diff --git a/activesupport/test/testing/performance_test.rb b/activesupport/test/testing/performance_test.rb
new file mode 100644
index 0000000000..53073cb8db
--- /dev/null
+++ b/activesupport/test/testing/performance_test.rb
@@ -0,0 +1,59 @@
+require 'abstract_unit'
+require 'active_support/testing/performance'
+
+
+module ActiveSupport
+ module Testing
+ class PerformanceTest < ActiveSupport::TestCase
+ def test_amount_format
+ amount_metric = ActiveSupport::Testing::Performance::Metrics[:amount].new
+ assert_equal "0", amount_metric.format(0)
+ assert_equal "1", amount_metric.format(1.23)
+ assert_equal "40,000,000", amount_metric.format(40000000)
+ end
+
+ def test_time_format
+ time_metric = ActiveSupport::Testing::Performance::Metrics[:time].new
+ assert_equal "0 ms", time_metric.format(0)
+ assert_equal "40 ms", time_metric.format(0.04)
+ assert_equal "41 ms", time_metric.format(0.0415)
+ assert_equal "1.23 sec", time_metric.format(1.23)
+ assert_equal "40000.00 sec", time_metric.format(40000)
+ assert_equal "-5000 ms", time_metric.format(-5)
+ end
+
+ def test_space_format
+ space_metric = ActiveSupport::Testing::Performance::Metrics[:digital_information_unit].new
+ assert_equal "0 Bytes", space_metric.format(0)
+ assert_equal "0 Bytes", space_metric.format(0.4)
+ assert_equal "1 Byte", space_metric.format(1.23)
+ assert_equal "123 Bytes", space_metric.format(123)
+ assert_equal "123 Bytes", space_metric.format(123.45)
+ assert_equal "12 KB", space_metric.format(12345)
+ assert_equal "1.2 MB", space_metric.format(1234567)
+ assert_equal "9.3 GB", space_metric.format(10**10)
+ assert_equal "91 TB", space_metric.format(10**14)
+ assert_equal "910000 TB", space_metric.format(10**18)
+ end
+
+ def test_environment_format_without_rails
+ metric = ActiveSupport::Testing::Performance::Metrics[:time].new
+ benchmarker = ActiveSupport::Testing::Performance::Benchmarker.new(self, metric)
+ assert_equal "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL},#{RUBY_PLATFORM}", benchmarker.environment
+ end
+
+ def test_environment_format_with_rails
+ rails, version = Module.new, Module.new
+ version.const_set :STRING, "4.0.0"
+ rails.const_set :VERSION, version
+ Object.const_set :Rails, rails
+
+ metric = ActiveSupport::Testing::Performance::Metrics[:time].new
+ benchmarker = ActiveSupport::Testing::Performance::Benchmarker.new(self, metric)
+ assert_equal "rails-4.0.0,#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL},#{RUBY_PLATFORM}", benchmarker.environment
+ ensure
+ Object.send :remove_const, :Rails
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/guides/Rakefile b/guides/Rakefile
index ad4ff91fe6..d005a12936 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -1,11 +1,71 @@
-desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
-task :generate_guides do
- ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
- ruby "rails_guides.rb"
-end
+namespace :guides do
+
+ desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
+ task :generate => 'generate:html'
+
+ namespace :generate do
+
+ desc "Generate HTML guides"
+ task :html do
+ ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
+ ruby "rails_guides.rb"
+ end
+
+ desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing"
+ task :kindle do
+ ENV['KINDLE'] = '1'
+ Rake::Task['guides:generate:html'].invoke
+ end
+ end
+
+ # Validate guides -------------------------------------------------------------------------
+ desc 'Validate guides, use ONLY=foo to process just "foo.html"'
+ task :validate do
+ ruby "w3c_validator.rb"
+ end
+
+ desc "Show help"
+ task :help do
+ puts <<-help
+
+Guides are taken from the source directory, and the resulting HTML goes into the
+output directory. Assets are stored under files, and copied to output/files as
+part of the generation process.
+
+All this process is handled via rake tasks, here's a full list of them:
-# Validate guides -------------------------------------------------------------------------
-desc 'Validate guides, use ONLY=foo to process just "foo.html"'
-task :validate_guides do
- ruby "w3c_validator.rb"
+#{%x[rake -T]}
+Some arguments may be passed via environment variables:
+
+ WARNINGS=1
+ Internal links (anchors) are checked, also detects duplicated IDs.
+
+ ALL=1
+ Force generation of all guides.
+
+ ONLY=name
+ Useful if you want to generate only one or a set of guides.
+
+ Generate only association_basics.html:
+ ONLY=assoc
+
+ Separate many using commas:
+ ONLY=assoc,migrations
+
+ GUIDES_LANGUAGE
+ Use it when you want to generate translated guides in
+ source/<GUIDES_LANGUAGE> folder (such as source/es)
+
+ EDGE=1
+ Indicate generated guides should be marked as edge.
+
+Examples:
+ $ rake guides:generate ALL=1
+ $ rake guides:generate EDGE=1
+ $ rake guides:generate:kindle EDGE=1
+ $ rake guides:generate GUIDES_LANGUAGE=es
+ help
+ end
end
+
+task :default => 'guides:help'
diff --git a/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png
index 44243edbca..43c963ffa8 100644
--- a/guides/assets/images/belongs_to.png
+++ b/guides/assets/images/belongs_to.png
Binary files differ
diff --git a/guides/assets/images/book_icon.gif b/guides/assets/images/book_icon.gif
index c81d5db520..efc5e06880 100644
--- a/guides/assets/images/book_icon.gif
+++ b/guides/assets/images/book_icon.gif
Binary files differ
diff --git a/guides/assets/images/challenge.png b/guides/assets/images/challenge.png
index d163748640..30be3d7028 100644
--- a/guides/assets/images/challenge.png
+++ b/guides/assets/images/challenge.png
Binary files differ
diff --git a/guides/assets/images/chapters_icon.gif b/guides/assets/images/chapters_icon.gif
index 06fb415f4a..a61c28c02d 100644
--- a/guides/assets/images/chapters_icon.gif
+++ b/guides/assets/images/chapters_icon.gif
Binary files differ
diff --git a/guides/assets/images/check_bullet.gif b/guides/assets/images/check_bullet.gif
index 1fcfeba250..bd54ef64c9 100644
--- a/guides/assets/images/check_bullet.gif
+++ b/guides/assets/images/check_bullet.gif
Binary files differ
diff --git a/guides/assets/images/credits_pic_blank.gif b/guides/assets/images/credits_pic_blank.gif
index f6f654fc65..a6b335d0c9 100644
--- a/guides/assets/images/credits_pic_blank.gif
+++ b/guides/assets/images/credits_pic_blank.gif
Binary files differ
diff --git a/guides/assets/images/csrf.png b/guides/assets/images/csrf.png
index ab73baafe8..a8123d47c3 100644
--- a/guides/assets/images/csrf.png
+++ b/guides/assets/images/csrf.png
Binary files differ
diff --git a/guides/assets/images/customized_error_messages.png b/guides/assets/images/customized_error_messages.png
index fa676991e3..fcf47b4be0 100644
--- a/guides/assets/images/customized_error_messages.png
+++ b/guides/assets/images/customized_error_messages.png
Binary files differ
diff --git a/guides/assets/images/edge_badge.png b/guides/assets/images/edge_badge.png
index cddd46c4b8..a35dc9f8ee 100644
--- a/guides/assets/images/edge_badge.png
+++ b/guides/assets/images/edge_badge.png
Binary files differ
diff --git a/guides/assets/images/error_messages.png b/guides/assets/images/error_messages.png
index 428892194a..1189e486d4 100644
--- a/guides/assets/images/error_messages.png
+++ b/guides/assets/images/error_messages.png
Binary files differ
diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png
index a26c09ef2d..1a13eddd91 100644
--- a/guides/assets/images/getting_started/confirm_dialog.png
+++ b/guides/assets/images/getting_started/confirm_dialog.png
Binary files differ
diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png
index badefe6ea6..6910e1647e 100644
--- a/guides/assets/images/getting_started/form_with_errors.png
+++ b/guides/assets/images/getting_started/form_with_errors.png
Binary files differ
diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png
index 6e58a13756..bf23cba231 100644
--- a/guides/assets/images/getting_started/index_action_with_edit_link.png
+++ b/guides/assets/images/getting_started/index_action_with_edit_link.png
Binary files differ
diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png
index dc9459032a..b573cb164c 100644
--- a/guides/assets/images/getting_started/new_post.png
+++ b/guides/assets/images/getting_started/new_post.png
Binary files differ
diff --git a/guides/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png
index bd9b2e10f5..e13095ff8f 100644
--- a/guides/assets/images/getting_started/post_with_comments.png
+++ b/guides/assets/images/getting_started/post_with_comments.png
Binary files differ
diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png
index 92a39efd78..407ea2ea06 100644
--- a/guides/assets/images/getting_started/routing_error_no_controller.png
+++ b/guides/assets/images/getting_started/routing_error_no_controller.png
Binary files differ
diff --git a/guides/assets/images/getting_started/routing_error_no_route_matches.png b/guides/assets/images/getting_started/routing_error_no_route_matches.png
index bc768a94a2..d461807c5d 100644
--- a/guides/assets/images/getting_started/routing_error_no_route_matches.png
+++ b/guides/assets/images/getting_started/routing_error_no_route_matches.png
Binary files differ
diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png
index 5c8c4d8e5e..9467df6a07 100644
--- a/guides/assets/images/getting_started/show_action_for_posts.png
+++ b/guides/assets/images/getting_started/show_action_for_posts.png
Binary files differ
diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png
index 9f070d59db..6860aaeca7 100644
--- a/guides/assets/images/getting_started/template_is_missing_posts_new.png
+++ b/guides/assets/images/getting_started/template_is_missing_posts_new.png
Binary files differ
diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png
index f568bf315c..c29cb2f54f 100644
--- a/guides/assets/images/getting_started/undefined_method_post_path.png
+++ b/guides/assets/images/getting_started/undefined_method_post_path.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
index 03d92dfb7d..1eca14b988 100644
--- a/guides/assets/images/getting_started/unknown_action_create_for_posts.png
+++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png
index b63883d922..fd72586573 100644
--- a/guides/assets/images/getting_started/unknown_action_new_for_posts.png
+++ b/guides/assets/images/getting_started/unknown_action_new_for_posts.png
Binary files differ
diff --git a/guides/assets/images/grey_bullet.gif b/guides/assets/images/grey_bullet.gif
index e75e8e93a1..3c08b1571c 100644
--- a/guides/assets/images/grey_bullet.gif
+++ b/guides/assets/images/grey_bullet.gif
Binary files differ
diff --git a/guides/assets/images/habtm.png b/guides/assets/images/habtm.png
index fea78b0b5c..b062bc73fe 100644
--- a/guides/assets/images/habtm.png
+++ b/guides/assets/images/habtm.png
Binary files differ
diff --git a/guides/assets/images/has_many.png b/guides/assets/images/has_many.png
index 6cff58460d..e7589e3b75 100644
--- a/guides/assets/images/has_many.png
+++ b/guides/assets/images/has_many.png
Binary files differ
diff --git a/guides/assets/images/has_many_through.png b/guides/assets/images/has_many_through.png
index 85d7599925..858c898dc1 100644
--- a/guides/assets/images/has_many_through.png
+++ b/guides/assets/images/has_many_through.png
Binary files differ
diff --git a/guides/assets/images/has_one.png b/guides/assets/images/has_one.png
index a70ddaaa86..93faa05b07 100644
--- a/guides/assets/images/has_one.png
+++ b/guides/assets/images/has_one.png
Binary files differ
diff --git a/guides/assets/images/has_one_through.png b/guides/assets/images/has_one_through.png
index 89a7617a30..07dac1a27d 100644
--- a/guides/assets/images/has_one_through.png
+++ b/guides/assets/images/has_one_through.png
Binary files differ
diff --git a/guides/assets/images/header_backdrop.png b/guides/assets/images/header_backdrop.png
index ff2982175e..72b030478f 100644
--- a/guides/assets/images/header_backdrop.png
+++ b/guides/assets/images/header_backdrop.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_html_safe.png b/guides/assets/images/i18n/demo_html_safe.png
index f881f60dac..9afa8ebec1 100644
--- a/guides/assets/images/i18n/demo_html_safe.png
+++ b/guides/assets/images/i18n/demo_html_safe.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_localized_pirate.png b/guides/assets/images/i18n/demo_localized_pirate.png
index 9134709573..bf8d0b558c 100644
--- a/guides/assets/images/i18n/demo_localized_pirate.png
+++ b/guides/assets/images/i18n/demo_localized_pirate.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_translated_en.png b/guides/assets/images/i18n/demo_translated_en.png
index ecdd878d38..e887bfa306 100644
--- a/guides/assets/images/i18n/demo_translated_en.png
+++ b/guides/assets/images/i18n/demo_translated_en.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_translated_pirate.png b/guides/assets/images/i18n/demo_translated_pirate.png
index 41c580923a..aa5618a865 100644
--- a/guides/assets/images/i18n/demo_translated_pirate.png
+++ b/guides/assets/images/i18n/demo_translated_pirate.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_translation_missing.png b/guides/assets/images/i18n/demo_translation_missing.png
index af9e2d0427..867aa7c42d 100644
--- a/guides/assets/images/i18n/demo_translation_missing.png
+++ b/guides/assets/images/i18n/demo_translation_missing.png
Binary files differ
diff --git a/guides/assets/images/i18n/demo_untranslated.png b/guides/assets/images/i18n/demo_untranslated.png
index 3603f43463..2ea6404822 100644
--- a/guides/assets/images/i18n/demo_untranslated.png
+++ b/guides/assets/images/i18n/demo_untranslated.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/1.png b/guides/assets/images/icons/callouts/1.png
index 7d473430b7..c5d02adcf4 100644
--- a/guides/assets/images/icons/callouts/1.png
+++ b/guides/assets/images/icons/callouts/1.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/10.png b/guides/assets/images/icons/callouts/10.png
index 997bbc8246..fe89f9ef83 100644
--- a/guides/assets/images/icons/callouts/10.png
+++ b/guides/assets/images/icons/callouts/10.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/11.png b/guides/assets/images/icons/callouts/11.png
index ce47dac3f5..9244a1ac4b 100644
--- a/guides/assets/images/icons/callouts/11.png
+++ b/guides/assets/images/icons/callouts/11.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/12.png b/guides/assets/images/icons/callouts/12.png
index 31daf4e2f2..ae56459f4c 100644
--- a/guides/assets/images/icons/callouts/12.png
+++ b/guides/assets/images/icons/callouts/12.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/13.png b/guides/assets/images/icons/callouts/13.png
index 14021a89c2..1181f9f892 100644
--- a/guides/assets/images/icons/callouts/13.png
+++ b/guides/assets/images/icons/callouts/13.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/14.png b/guides/assets/images/icons/callouts/14.png
index 64014b75fe..4274e6580a 100644
--- a/guides/assets/images/icons/callouts/14.png
+++ b/guides/assets/images/icons/callouts/14.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/15.png b/guides/assets/images/icons/callouts/15.png
index 0d65765fcf..39304de94f 100644
--- a/guides/assets/images/icons/callouts/15.png
+++ b/guides/assets/images/icons/callouts/15.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/2.png b/guides/assets/images/icons/callouts/2.png
index 5d09341b2f..8c57970ba9 100644
--- a/guides/assets/images/icons/callouts/2.png
+++ b/guides/assets/images/icons/callouts/2.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/3.png b/guides/assets/images/icons/callouts/3.png
index ef7b700471..57a33d15b4 100644
--- a/guides/assets/images/icons/callouts/3.png
+++ b/guides/assets/images/icons/callouts/3.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/4.png b/guides/assets/images/icons/callouts/4.png
index adb8364eb5..f061ab02b8 100644
--- a/guides/assets/images/icons/callouts/4.png
+++ b/guides/assets/images/icons/callouts/4.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/5.png b/guides/assets/images/icons/callouts/5.png
index 4d7eb46002..b4de02da11 100644
--- a/guides/assets/images/icons/callouts/5.png
+++ b/guides/assets/images/icons/callouts/5.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/6.png b/guides/assets/images/icons/callouts/6.png
index 0ba694af6c..0e055eec1e 100644
--- a/guides/assets/images/icons/callouts/6.png
+++ b/guides/assets/images/icons/callouts/6.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/7.png b/guides/assets/images/icons/callouts/7.png
index 472e96f8ac..5ead87d040 100644
--- a/guides/assets/images/icons/callouts/7.png
+++ b/guides/assets/images/icons/callouts/7.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/8.png b/guides/assets/images/icons/callouts/8.png
index 5e60973c21..cb99545eb6 100644
--- a/guides/assets/images/icons/callouts/8.png
+++ b/guides/assets/images/icons/callouts/8.png
Binary files differ
diff --git a/guides/assets/images/icons/callouts/9.png b/guides/assets/images/icons/callouts/9.png
index a0676d26cc..0ac03602f6 100644
--- a/guides/assets/images/icons/callouts/9.png
+++ b/guides/assets/images/icons/callouts/9.png
Binary files differ
diff --git a/guides/assets/images/icons/caution.png b/guides/assets/images/icons/caution.png
index cb9d5ea0df..031e19c776 100644
--- a/guides/assets/images/icons/caution.png
+++ b/guides/assets/images/icons/caution.png
Binary files differ
diff --git a/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png
index bba1c0010d..1b0e482059 100644
--- a/guides/assets/images/icons/example.png
+++ b/guides/assets/images/icons/example.png
Binary files differ
diff --git a/guides/assets/images/icons/home.png b/guides/assets/images/icons/home.png
index 37a5231bac..24149d6e78 100644
--- a/guides/assets/images/icons/home.png
+++ b/guides/assets/images/icons/home.png
Binary files differ
diff --git a/guides/assets/images/icons/important.png b/guides/assets/images/icons/important.png
index 1096c23295..dafcf0f59e 100644
--- a/guides/assets/images/icons/important.png
+++ b/guides/assets/images/icons/important.png
Binary files differ
diff --git a/guides/assets/images/icons/next.png b/guides/assets/images/icons/next.png
index 64e126bdda..355b329f5a 100644
--- a/guides/assets/images/icons/next.png
+++ b/guides/assets/images/icons/next.png
Binary files differ
diff --git a/guides/assets/images/icons/note.png b/guides/assets/images/icons/note.png
index 841820f7c4..08d35a6f5c 100644
--- a/guides/assets/images/icons/note.png
+++ b/guides/assets/images/icons/note.png
Binary files differ
diff --git a/guides/assets/images/icons/prev.png b/guides/assets/images/icons/prev.png
index 3e8f12fe24..ea564c865e 100644
--- a/guides/assets/images/icons/prev.png
+++ b/guides/assets/images/icons/prev.png
Binary files differ
diff --git a/guides/assets/images/icons/tip.png b/guides/assets/images/icons/tip.png
index a3a029d898..d834e6d1bb 100644
--- a/guides/assets/images/icons/tip.png
+++ b/guides/assets/images/icons/tip.png
Binary files differ
diff --git a/guides/assets/images/icons/up.png b/guides/assets/images/icons/up.png
index 2db1ce62fa..379f0045af 100644
--- a/guides/assets/images/icons/up.png
+++ b/guides/assets/images/icons/up.png
Binary files differ
diff --git a/guides/assets/images/icons/warning.png b/guides/assets/images/icons/warning.png
index 0b0c419df2..72a8a5d873 100644
--- a/guides/assets/images/icons/warning.png
+++ b/guides/assets/images/icons/warning.png
Binary files differ
diff --git a/guides/assets/images/nav_arrow.gif b/guides/assets/images/nav_arrow.gif
index c4f57658d7..ff081819ad 100644
--- a/guides/assets/images/nav_arrow.gif
+++ b/guides/assets/images/nav_arrow.gif
Binary files differ
diff --git a/guides/assets/images/oscardelben.jpg b/guides/assets/images/oscardelben.jpg
new file mode 100644
index 0000000000..9f3f67c2c7
--- /dev/null
+++ b/guides/assets/images/oscardelben.jpg
Binary files differ
diff --git a/guides/assets/images/polymorphic.png b/guides/assets/images/polymorphic.png
index ff2fd9f76d..a3cbc4502a 100644
--- a/guides/assets/images/polymorphic.png
+++ b/guides/assets/images/polymorphic.png
Binary files differ
diff --git a/guides/assets/images/rails_guides_logo.gif b/guides/assets/images/rails_guides_logo.gif
index a24683a34e..9b0ad5af28 100644
--- a/guides/assets/images/rails_guides_logo.gif
+++ b/guides/assets/images/rails_guides_logo.gif
Binary files differ
diff --git a/guides/assets/images/rails_welcome.png b/guides/assets/images/rails_welcome.png
index f2aa210d19..8ad2d351de 100644
--- a/guides/assets/images/rails_welcome.png
+++ b/guides/assets/images/rails_welcome.png
Binary files differ
diff --git a/guides/assets/images/session_fixation.png b/guides/assets/images/session_fixation.png
index 6b084508db..ac3ab01614 100644
--- a/guides/assets/images/session_fixation.png
+++ b/guides/assets/images/session_fixation.png
Binary files differ
diff --git a/guides/assets/images/tab_grey.gif b/guides/assets/images/tab_grey.gif
index e9680b7136..995adb76cf 100644
--- a/guides/assets/images/tab_grey.gif
+++ b/guides/assets/images/tab_grey.gif
Binary files differ
diff --git a/guides/assets/images/tab_info.gif b/guides/assets/images/tab_info.gif
index 458fea9a61..e9dd164f18 100644
--- a/guides/assets/images/tab_info.gif
+++ b/guides/assets/images/tab_info.gif
Binary files differ
diff --git a/guides/assets/images/tab_note.gif b/guides/assets/images/tab_note.gif
index 1d5c171ed6..f9b546c6f8 100644
--- a/guides/assets/images/tab_note.gif
+++ b/guides/assets/images/tab_note.gif
Binary files differ
diff --git a/guides/assets/images/tab_red.gif b/guides/assets/images/tab_red.gif
index daf140b5a8..0613093ddc 100644
--- a/guides/assets/images/tab_red.gif
+++ b/guides/assets/images/tab_red.gif
Binary files differ
diff --git a/guides/assets/images/tab_yellow.gif b/guides/assets/images/tab_yellow.gif
index dc961c99dd..39a3c2dc6a 100644
--- a/guides/assets/images/tab_yellow.gif
+++ b/guides/assets/images/tab_yellow.gif
Binary files differ
diff --git a/guides/assets/images/tab_yellow.png b/guides/assets/images/tab_yellow.png
index cceea6581f..3ab1c56c4d 100644
--- a/guides/assets/images/tab_yellow.png
+++ b/guides/assets/images/tab_yellow.png
Binary files differ
diff --git a/guides/assets/images/validation_error_messages.png b/guides/assets/images/validation_error_messages.png
index 622d35da5d..30e4ca4a3d 100644
--- a/guides/assets/images/validation_error_messages.png
+++ b/guides/assets/images/validation_error_messages.png
Binary files differ
diff --git a/guides/code/getting_started/app/controllers/home_controller.rb b/guides/code/getting_started/app/controllers/welcome_controller.rb
index 309b70441e..f9b859b9c9 100644
--- a/guides/code/getting_started/app/controllers/home_controller.rb
+++ b/guides/code/getting_started/app/controllers/welcome_controller.rb
@@ -1,5 +1,4 @@
class WelcomeController < ApplicationController
def index
end
-
end
diff --git a/guides/code/getting_started/test/functional/home_controller_test.rb b/guides/code/getting_started/test/functional/welcome_controller_test.rb
index dff8e9d2c5..e4d5abae11 100644
--- a/guides/code/getting_started/test/functional/home_controller_test.rb
+++ b/guides/code/getting_started/test/functional/welcome_controller_test.rb
@@ -5,5 +5,4 @@ class WelcomeControllerTest < ActionController::TestCase
get :index
assert_response :success
end
-
end
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index d6a98f9ac4..230bebf3bb 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -1,7 +1,7 @@
# ---------------------------------------------------------------------------
#
-# This script generates the guides. It can be invoked either directly or via the
-# generate_guides rake task within the railties directory.
+# This script generates the guides. It can be invoked via the
+# guides:generate rake task within the guides directory.
#
# Guides are taken from the source directory, and the resulting HTML goes into the
# output directory. Assets are stored under files, and copied to output/files as
@@ -47,11 +47,6 @@
# Set to "1" to indicate generated guides should be marked as edge. This
# inserts a badge and changes the preamble of the home page.
#
-# KINDLE
-# Set to "1" to generate the .mobi with all the guides. The kindlegen
-# executable must be in your PATH. You can get it for free from
-# http://www.amazon.com/kindlepublishing
-#
# ---------------------------------------------------------------------------
require 'set'
diff --git a/guides/source/4_0_release_notes.textile b/guides/source/4_0_release_notes.textile
new file mode 100644
index 0000000000..1ed6006b6b
--- /dev/null
+++ b/guides/source/4_0_release_notes.textile
@@ -0,0 +1,591 @@
+h2. Ruby on Rails 4.0 Release Notes
+
+Highlights in Rails 4.0:
+
+These release notes cover the major changes, but do not include each bug-fix and changes. If you want to see everything, check out the "list of commits":https://github.com/rails/rails/commits/master in the main Rails repository on GitHub.
+
+endprologue.
+
+h3. Upgrading to Rails 4.0
+
+TODO. This is a WIP guide.
+
+If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3.2 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 4.0. Then take heed of the following changes:
+
+h4. Rails 4.0 requires at least Ruby 1.9.3
+
+Rails 4.0 requires Ruby 1.9.3 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible.
+
+h4. What to update in your apps
+
+* Update your Gemfile to depend on
+** <tt>rails = 4.0.0</tt>
+** <tt>sass-rails ~> 3.2.3</tt>
+** <tt>coffee-rails ~> 3.2.1</tt>
+** <tt>uglifier >= 1.0.3</tt>
+
+TODO: Update the versions above.
+
+* Rails 4.0 removes <tt>vendor/plugins</tt> completely. You have to replace these plugins by extracting them as gems and adding them in your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
+
+TODO: Configuration changes in environment files
+
+h3. Creating a Rails 4.0 application
+
+<shell>
+# You should have the 'rails' rubygem installed
+$ rails new myapp
+$ cd myapp
+</shell>
+
+h4. Vendoring Gems
+
+Rails now uses a +Gemfile+ in the application root to determine the gems you require for your application to start. This +Gemfile+ is processed by the "Bundler":https://github.com/carlhuda/bundler gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
+
+More information: "Bundler homepage":http://gembundler.com
+
+h4. Living on the Edge
+
++Bundler+ and +Gemfile+ makes freezing your Rails application easy as pie with the new dedicated +bundle+ command. If you want to bundle straight from the Git repository, you can pass the +--edge+ flag:
+
+<shell>
+$ rails new myapp --edge
+</shell>
+
+If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag:
+
+<shell>
+$ ruby /path/to/rails/railties/bin/rails new myapp --dev
+</shell>
+
+h3. Major Features
+
+h3. Documentation
+
+h3. Railties
+
+* Add runner to <tt>Rails::Railtie</tt> as a hook called just after runner starts.
+
+* Add <tt>/rails/info/routes</tt> path which displays the same information as +rake routes+.
+
+* Improved +rake routes+ output for redirects.
+
+* Load all environments available in config.paths["config/environments"].
+
+* The application generator generates <tt>public/humans.txt</tt> with some basic data.
+
+* Add <tt>config.queue_consumer</tt> to allow the default consumer to be configurable.
+
+* Add <tt>Rails.queue</tt> as an interface with a default implementation that consumes jobs in a separate thread.
+
+* Remove <tt>Rack::SSL</tt> in favour of <tt>ActionDispatch::SSL</tt>.
+
+* Allow to set class that will be used to run as a console, other than IRB, with <tt>Rails.application.config.console=</tt>. It's best to add it to console block.
+
+<ruby>
+# it can be added to config/application.rb
+console do
+ # this block is called only when running console,
+ # so we can safely require pry here
+ require "pry"
+ config.console = Pry
+end
+</ruby>
+
+* Add convenience hide! method to Rails generators to hide current generator
+ namespace from showing when running rails generate.
+
+* Scaffold now uses +content_tag_for+ in <tt>index.html.erb</tt>.
+
+* <tt>Rails::Plugin</tt> is removed. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies.
+
+h4(#railties_deprecations). Deprecations
+
+h3. Action Mailer
+
+* No changes.
+
+h3. Action Pack
+
+h4. Action Controller
+
+* Extracted redirect logic from <tt>ActionController::ForceSSL::ClassMethods.force_ssl</tt> into <tt>ActionController::ForceSSL#force_ssl_redirect</tt>.
+
+* URL path parameters with invalid encoding now raise <tt>ActionController::BadRequest</tt>.
+
+* Malformed query and request parameter hashes now raise <tt>ActionController::BadRequest</tt>.
+
+* +respond_to+ and +respond_with+ now raise <tt>ActionController::UnknownFormat</tt> instead of directly returning head 406. The exception is rescued and converted to 406 in the exception handling middleware.
+
+* JSONP now uses mimetype <tt>application/javascript</tt> instead of <tt>application/json</tt>.
+
+* Session arguments passed to process calls in functional tests are now merged into the existing session, whereas previously they would replace the existing session. This change may break some existing tests if they are asserting the exact contents of the session but should not break existing tests that only assert individual keys.
+
+* Forms of persisted records use always PATCH (via the _method hack).
+
+* For resources, both PATCH and PUT are routed to the update action.
+
+* Don't ignore +force_ssl+ in development. This is a change of behavior - use a :if condition to recreate the old behavior.
+
+<ruby>
+class AccountsController < ApplicationController
+ force_ssl :if => :ssl_configured?
+
+ def ssl_configured?
+ !Rails.env.development?
+ end
+end
+</ruby>
+
+h5(#actioncontroller_deprecations). Deprecations
+
+* Deprecated ActionController::Integration in favour of ActionDispatch::Integration
+
+* Deprecated ActionController::IntegrationTest in favour of ActionDispatch::IntegrationTest
+
+* Deprecated ActionController::PerformanceTest in favour of ActionDispatch::PerformanceTest
+
+* Deprecated ActionController::AbstractRequest in favour of ActionDispatch::Request
+
+* Deprecated ActionController::Request in favour of ActionDispatch::Request
+
+* Deprecated ActionController::AbstractResponse in favour of ActionDispatch::Response
+
+* Deprecated ActionController::Response in favour of ActionDispatch::Response
+
+* Deprecated ActionController::Routing in favour of ActionDispatch::Routing
+
+h4. Action Dispatch
+
+* Added <tt>ActionDispatch::SSL</tt> middleware that when included force all the requests to be under HTTPS protocol.
+
+* Copy literal route constraints to defaults so that url generation know about them. The copied constraints are :protocol, :subdomain, :domain, :host and :port.
+
+* Allows +assert_redirected_to+ to match against a regular expression.
+
+* Add backtrace to development routing error page.
+
+* +assert_generates+, +assert_recognizes+, and +assert_routing+ all raise +Assertion+ instead of +RoutingError+.
+
+* Allows the route helper root to take a string argument. For example, <tt>root 'pages#main'</tt> as a shortcut for <tt>root to: 'pages#main'</tt>.
+
+* Adds support for the PATCH verb:
+ Request objects respond to patch?.
+ Routes have a new patch method, and understand :patch in the
+ existing places where a verb is configured, like :via.
+ New method patch available in functional tests.
+ If :patch is the default verb for updates, edits are
+ tunneled as PATCH rather than as PUT, and routing acts accordingly.
+ New method patch_via_redirect available in integration tests.
+
+* Integration tests support the OPTIONS method.
+
+* +expires_in+ accepts a +must_revalidate+ flag. If true, "must-revalidate" is added to the Cache-Control header.
+
+* Default responder will now always use your overridden block in respond_with to render your response.
+
+* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to check that info.
+
+* Include mounted_helpers (helpers for accessing mounted engines) in <tt>ActionDispatch::IntegrationTest</tt> by default.
+
+h5(#actiondispatch_deprecations). Deprecations
+
+h4. Action View
+
+* Make current object and counter (when it applies) variables accessible when rendering templates with :object / :collection.
+
+* Allow to lazy load +default_form_builder+ by passing a String instead of a constant.
+
+* Add index method to +FormBuilder+ class.
+
+* Adds support for layouts when rendering a partial with a given collection.
+
+* Remove <tt>:disable_with</tt> in favor of <tt>data-disable-with</tt> option from +submit_tag+, +button_tag+ and +button_to+ helpers.
+
+* Remove <tt>:mouseover</tt> option from +image_tag+ helper.
+
+* Templates without a handler extension now raises a deprecation warning but still defaults to ERb. In future releases, it will simply return the template content.
+
+* Add divider option to +grouped_options_for_select+ to generate a separator optgroup automatically, and deprecate prompt as third argument, in favor of using an options hash.
+
+* Add +time_field+ and +time_field_tag+ helpers which render an <tt>input[type="time"]</tt> tag.
+
+* Removed old +text_helper+ apis for +highlight+, +excerpt+ and +word_wrap+.
+
+* Allow to use mounted_helpers (helpers for accessing mounted engines) in <tt>ActionView::TestCase</tt>.
+
+* Remove the leading \n added by textarea on +assert_select+.
+
+* Changed default value for <tt>config.action_view.embed_authenticity_token_in_remote_forms</tt> to false. This change breaks remote forms that need to work also without JavaScript, so if you need such behavior, you can either set it to true or explicitly pass <tt>:authenticity_token => true</tt> in form options.
+
+* Make possible to use a block in +button_to+ helper if button text is hard to fit into the name parameter:
+
+<ruby>
+<%= button_to [:make_happy, @user] do %>
+ Make happy <strong><%= @user.name %></strong>
+<% end %>
+# => "<form method="post" action="/users/1/make_happy" class="button_to">
+# <div>
+# <button type="submit">
+# Make happy <strong>Name</strong>
+# </button>
+# </div>
+# </form>"
+</ruby>
+
+* Replace +include_seconds+ boolean argument with <tt>:include_seconds => true</tt> option in +distance_of_time_in_words+ and +time_ago_in_words+ signature.
+
+* Remove +button_to_function+ and +link_to_function+ helpers.
+
+* truncate now always returns an escaped HTML-safe string. The option :escape can be used as false to not escape the result.
+
+* truncate now accepts a block to show extra content when the text is truncated.
+
+* Add +week_field+, +week_field_tag+, +month_field+, +month_field_tag+, +datetime_local_field+, +datetime_local_field_tag+, +datetime_field+ and +datetime_field_tag+ helpers.
+
+* Add +color_field+ and +color_field_tag+ helpers.
+
+* Add +include_hidden+ option to select tag. With <tt>:include_hidden => false</tt> select with multiple attribute doesn't generate hidden input with blank value.
+
+* Removed default size option from the +text_field+, +search_field+, +telephone_field+, +url_field+, +email_field+ helpers.
+
+* Removed default cols and rows options from the +text_area+ helper.
+
+* Adds +image_url+, +javascript_url+, +stylesheet_url+, +audio_url+, +video_url+, and +font_url+ to assets tag helper. These URL helpers will return the full path to your assets. This is useful when you are going to reference this asset from external host.
+
+* Allow +value_method+ and +text_method+ arguments from +collection_select+ and +options_from_collection_for_select+ to receive an object that responds to :call, such as a proc, to evaluate the option in the current element context. This works the same way with +collection_radio_buttons+ and +collection_check_boxes+.
+
+* Add +date_field+ and +date_field_tag+ helpers which render an <tt>input[type="date"]</tt> tag.
+
+* Add +collection_check_boxes+ form helper, similar to +collection_select+:
+
+<ruby>
+collection_check_boxes :post, :author_ids, Author.all, :id, :name
+# Outputs something like:
+<input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
+<label for="post_author_ids_1">D. Heinemeier Hansson</label>
+<input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+<label for="post_author_ids_2">D. Thomas</label>
+<input name="post[author_ids][]" type="hidden" value="" />
+</ruby>
+
+The label/check_box pairs can be customized with a block.
+
+* Add +collection_radio_buttons+ form helper, similar to collection_select:
+
+<ruby>
+collection_radio_buttons :post, :author_id, Author.all, :id, :name
+# Outputs something like:
+<input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
+<label for="post_author_id_1">D. Heinemeier Hansson</label>
+<input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+<label for="post_author_id_2">D. Thomas</label>
+</ruby>
+
+The label/radio_button pairs can be customized with a block.
+
+* +check_box+ with :form HTML5 attribute will now replicate the :form attribute to the hidden field as well.
+
+* label form helper accepts :for => nil to not generate the attribute.
+
+* Add :format option to +number_to_percentage+.
+
+* Add <tt>config.action_view.logger</tt> to configure logger for Action View.
+
+* +check_box+ helper with :disabled => true will generate a disabled hidden field to conform with the HTML convention where disabled fields are not submitted with the form. This is a behavior change, previously the hidden tag had a value of the disabled checkbox.
+
+* +favicon_link_tag+ helper will now use the favicon in <tt>app/assets</tt> by default.
+
+* <tt>ActionView::Helpers::TextHelper#highlight</tt> now defaults to the HTML5 +mark+ element.
+
+h5(#actionview_deprecations). Deprecations
+
+h4. Sprockets
+
+Moved into a separate gem <tt>sprockets-rails</tt>.
+
+h3. Active Record
+
+* Allow blocks for count with <tt>ActiveRecord::Relation</tt>, to work similar as <tt>Array#count</tt>: <tt>Person.where("age > 26").count { |person| person.gender == 'female' }</tt>
+
+* Added support to <tt>CollectionAssociation#delete</tt> for passing fixnum or string values as record ids. This finds the records responding to the id and executes delete on them.
+
+<ruby>
+class Person < ActiveRecord::Base
+ has_many :pets
+end
+
+person.pets.delete("1") # => [#<Pet id: 1>]
+person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
+</ruby>
+
+* It's not possible anymore to destroy a model marked as read only.
+
+* Added ability to <tt>ActiveRecord::Relation#from</tt> to accept other <tt>ActiveRecord::Relation</tt> objects.
+
+* Added custom coders support for <tt>ActiveRecord::Store</tt>. Now you can set your custom coder like this:
+
+<ruby>store :settings, accessors: [ :color, :homepage ], coder: JSON</ruby>
+
+* mysql and mysql2 connections will set SQL_MODE=STRICT_ALL_TABLES by default to avoid silent data loss. This can be disabled by specifying strict: false in your database.yml.
+
+* Added default order to first to assure consistent results among diferent database engines. Introduced take as a replacement to the old behavior of first.
+
+* Added an :index option to automatically create indexes for references and belongs_to statements in migrations. The references and belongs_to methods now support an index option that receives either a boolean value or an options hash that is identical to options available to the add_index method:
+
+<ruby>
+create_table :messages do |t|
+ t.references :person, :index => true
+end
+</ruby>
+
+Is the same as:
+
+<ruby>
+create_table :messages do |t|
+ t.references :person
+end
+add_index :messages, :person_id
+</ruby>
+
+Generators have also been updated to use the new syntax.
+
+* Added bang methods for mutating <tt>ActiveRecord::Relation</tt> objects. For example, while <tt>foo.where(:bar)</tt> will return a new object leaving foo unchanged, <tt>foo.where!(:bar)</tt> will mutate the foo object.
+
+* Added #find_by and #find_by! to mirror the functionality provided by dynamic finders in a way that allows dynamic input more easily:
+
+<ruby>
+Post.find_by name: 'Spartacus', rating: 4
+Post.find_by "published_at < ?", 2.weeks.ago
+Post.find_by! name: 'Spartacus'
+</ruby>
+
+* Added <tt>ActiveRecord::Base#slice</tt> to return a hash of the given methods with their names as keys and returned values as values.
+
+* Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this commit: https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed.
+
+* Added a feature to dump/load internal state of SchemaCache instance because we want to boot rails more quickly when we have many models.
+
+<ruby>
+# execute rake task.
+RAILS_ENV=production bundle exec rake db:schema:cache:dump
+=> generate db/schema_cache.dump
+
+# add config.use_schema_cache_dump = true in config/production.rb. BTW, true is default.
+
+# boot rails.
+RAILS_ENV=production bundle exec rails server
+=> use db/schema_cache.dump
+
+# If you remove clear dumped cache, execute rake task.
+RAILS_ENV=production bundle exec rake db:schema:cache:clear
+=> remove db/schema_cache.dump
+</ruby>
+
+* Added support for partial indices to PostgreSQL adapter.
+
+* The +add_index+ method now supports a +where+ option that receives a string with the partial index criteria.
+
+* Implemented <tt>ActiveRecord::Relation#none</tt> method which returns a chainable relation with zero records (an instance of the NullRelation class). Any subsequent condition chained to the returned relation will continue generating an empty relation and will not fire any query to the database.
+
+* Added the <tt>ActiveRecord::NullRelation</tt> class implementing the null object pattern for the Relation class.
+
+* Added +create_join_table+ migration helper to create HABTM join tables.
+
+<ruby>
+create_join_table :products, :categories
+# =>
+# create_table :categories_products, :id => false do |td|
+# td.integer :product_id, :null => false
+# td.integer :category_id, :null => false
+# end
+</ruby>
+
+* The primary key is always initialized in the @attributes hash to nil (unless another value has been specified).
+
+* In previous releases, the following would generate a single query with an OUTER JOIN comments, rather than two separate queries:
+
+<ruby>Post.includes(:comments).where("comments.name = 'foo'")</ruby>
+
+This behaviour relies on matching SQL string, which is an inherently flawed idea unless we write an SQL parser, which we do not wish to do. Therefore, it is now deprecated.
+
+To avoid deprecation warnings and for future compatibility, you must explicitly state which tables you reference, when using SQL snippets:
+
+<ruby>Post.includes(:comments).where("comments.name = 'foo'").references(:comments)</ruby>
+
+Note that you do not need to explicitly specify references in the following cases, as they can be automatically inferred:
+
+<ruby>
+Post.where(comments: { name: 'foo' })
+Post.where('comments.name' => 'foo')
+Post.order('comments.name')
+</ruby>
+
+You also do not need to worry about this unless you are doing eager loading. Basically, don't worry unless you see a deprecation warning or (in future releases) an SQL error due to a missing JOIN.
+
+* Support for the +schema_info+ table has been dropped. Please switch to +schema_migrations+.
+
+* Connections *must* be closed at the end of a thread. If not, your connection pool can fill and an exception will be raised.
+
+* Added the <tt>ActiveRecord::Model</tt> module which can be included in a class as an alternative to inheriting from <tt>ActiveRecord::Base</tt>:
+
+<ruby>
+class Post
+ include ActiveRecord::Model
+end
+</ruby>
+
+* PostgreSQL hstore records can be created.
+
+* PostgreSQL hstore types are automatically deserialized from the database.
+
+h4(#activerecord_deprecations). Deprecations
+
+* Deprecated most of the 'dynamic finder' methods. All dynamic methods except for +find_by_...+ and +find_by_...!+ are deprecated. Here's how you can rewrite the code:
+
+<ruby>
+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 where(...).first_or_create
+find_or_create_by_...! can be rewritten using where(...).first_or_create!
+</ruby>
+
+The implementation of the deprecated dynamic finders has been moved to the +active_record_deprecated_finders+ gem.
+
+* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do. For example this:
+
+<ruby>Post.find(:all, :conditions => { :comments_count => 10 }, :limit => 5)</ruby>
+
+should be rewritten in the new style which has existed since Rails 3:
+
+<ruby>Post.where(comments_count: 10).limit(5)</ruby>
+
+Note that as an interim step, it is possible to rewrite the above as:
+
+<ruby>Post.scoped(:where => { :comments_count => 10 }, :limit => 5)</ruby>
+
+This could save you a lot of work if there is a lot of old-style finder usage in your application.
+
+Calling <tt>Post.scoped(options)</tt> is a shortcut for <tt>Post.scoped.merge(options)</tt>. <tt>Relation#merge</tt> now accepts a hash of options, but they must be identical to the names of the equivalent finder method. These are mostly identical to the old-style finder option names, except in the following cases:
+
+<plain>
+:conditions becomes :where
+:include becomes :includes
+:extend becomes :extending
+</plain>
+
+The code to implement the deprecated features has been moved out to the +active_record_deprecated_finders+ gem. This gem is a dependency of Active Record in Rails 4.0. It will no longer be a dependency from Rails 4.1, but if your app relies on the deprecated features then you can add it to your own Gemfile. It will be maintained by the Rails core team until Rails 5.0 is released.
+
+* Deprecate eager-evaluated scopes.
+
+ Don't use this:
+
+ scope :red, where(color: 'red')
+ default_scope where(color: 'red')
+
+ Use this:
+
+ scope :red, -> { where(color: 'red') }
+ default_scope { where(color: 'red') }
+
+ The former has numerous issues. It is a common newbie gotcha to do the following:
+
+ scope :recent, where(published_at: Time.now - 2.weeks)
+
+ Or a more subtle variant:
+
+ scope :recent, -> { where(published_at: Time.now - 2.weeks) }
+ scope :recent_red, recent.where(color: 'red')
+
+ Eager scopes are also very complex to implement within Active Record, and there are still bugs. For example, the following does not do what you expect:
+
+ scope :remove_conditions, except(:where)
+ where(...).remove_conditions # => still has conditions
+
+* Added deprecation for the :dependent => :restrict association option.
+
+* Up until now has_many and has_one, :dependent => :restrict option raised a DeleteRestrictionError at the time of destroying the object. Instead, it will add an error on the model.
+
+* To fix this warning, make sure your code isn't relying on a DeleteRestrictionError and then add config.active_record.dependent_restrict_raises = false to your application config.
+
+* New rails application would be generated with the config.active_record.dependent_restrict_raises = false in the application config.
+
+h3. Active Model
+
+* Passing false hash values to +validates+ will no longer enable the corresponding validators.
+
+* +ConfirmationValidator+ error messages will attach to <tt>:#{attribute}_confirmation</tt> instead of +attribute+.
+
+* Added <tt>ActiveModel::Model</tt>, a mixin to make Ruby objects work with Action Pack out of the box.
+
+* <tt>ActiveModel::Errors#to_json</tt> supports a new parameter <tt>:full_messages</tt>.
+
+* Trims down the API by removing <tt>valid?</tt> and <tt>errors.full_messages</tt>.
+
+h4(#activemodel_deprecations). Deprecations
+
+h3. Active Resource
+
+* Active Resource is removed from Rails 4.0 and is now a separate gem. TODO: put a link to the gem here.
+
+h3. Active Support
+
+* <tt>ActionView::Helpers::NumberHelper</tt> methods have been moved to <tt>ActiveSupport::NumberHelper</tt> and are now available via <tt>Numeric#to_s</tt>.
+
+* <tt>Numeric#to_s</tt> now accepts the formatting options :phone, :currency, :percentage, :delimited, :rounded, :human, and :human_size.
+
+* Add <tt>Hash#transform_keys</tt>, <tt>Hash#transform_keys!</tt>, <tt>Hash#deep_transform_keys</tt> and <tt>Hash#deep_transform_keys!</tt>.
+
+* Changed xml type datetime to dateTime (with upper case letter T).
+
+* Add <tt>:instance_accessor</tt> option for <tt>class_attribute</tt>.
+
+* +constantize+ now looks in the ancestor chain.
+
+* Add <tt>Hash#deep_stringify_keys</tt> and <tt>Hash#deep_stringify_keys!</tt> to convert all keys from a +Hash+ instance into strings.
+
+* Add <tt>Hash#deep_symbolize_keys</tt> and <tt>Hash#deep_symbolize_keys!</tt> to convert all keys from a +Hash+ instance into symbols.
+
+* <tt>Object#try</tt> can't call private methods.
+
+* AS::Callbacks#run_callbacks remove key argument.
+
+* +deep_dup+ works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances.
+
+* Inflector no longer applies ice -> ouse to words like slice, police.
+
+* Add <tt>ActiveSupport::Deprecations.behavior = :silence</tt> to completely ignore Rails runtime deprecations.
+
+* Make <tt>Module#delegate</tt> stop using send - can no longer delegate to private methods.
+
+* AS::Callbacks deprecate :rescuable option.
+
+* Adds <tt>Integer#ordinal</tt> to get the ordinal suffix string of an integer.
+
+* AS::Callbacks :per_key option is no longer supported.
+
+* AS::Callbacks#define_callbacks add :skip_after_callbacks_if_terminated option.
+
+* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it.
+
+* Remove <tt>ActiveSupport::TestCase#pending</tt> method, use +skip+ instead.
+
+* Deletes the compatibility method <tt>Module#method_names</tt>, use <tt>Module#methods</tt> from now on (which returns symbols).
+
+* Deletes the compatibility method <tt>Module#instance_method_names</tt>, use <tt>Module#instance_methods</tt> from now on (which returns symbols).
+
+* Unicode database updated to 6.1.0.
+
+* Adds +encode_big_decimal_as_string+ option to force JSON serialization of BigDecimals as numeric instead of wrapping them in strings for safety.
+
+h4(#activesupport_deprecations). Deprecations
+
+* <tt>BufferedLogger</tt> is deprecated. Use <tt>ActiveSupport::Logger</tt> or the +logger+ from Ruby stdlib.
+
+* Deprecates the compatibility method <tt>Module#local_constant_names</tt> and use <tt>Module#local_constants</tt> instead (which returns symbols).
+
+h3. Credits
+
+See the "full list of contributors to Rails":http://contributors.rubyonrails.org/ for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them.
diff --git a/guides/source/action_view_overview.textile b/guides/source/action_view_overview.textile
index bde30ba21c..fdfa97effa 100644
--- a/guides/source/action_view_overview.textile
+++ b/guides/source/action_view_overview.textile
@@ -454,7 +454,7 @@ input("post", "title") # =>
h4. RecordTagHelper
-This module provides methods for generating a container tag, such as a +<div>+, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use.
+This module provides methods for generating container tags, such as +div+, for your record. This is the recommended way of creating a container for render your Active Record object, as it adds an appropriate class and id attributes to that container. You can then refer to those containers easily by following the convention, instead of having to think about which class or id attribute you should use.
h5. content_tag_for
@@ -542,28 +542,28 @@ image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png
h5. register_javascript_expansion
-Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in +public/javascripts+.
+Register one or more JavaScript files to be included when symbol is passed to javascript_include_tag. This method is typically intended to be called from plugin initialization to register JavaScript files that the plugin installed in +vendor/assets/javascripts+.
<ruby>
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
javascript_include_tag :monkey # =>
- <script src="/javascripts/head.js"></script>
- <script src="/javascripts/body.js"></script>
- <script src="/javascripts/tail.js"></script>
+ <script src="/assets/head.js"></script>
+ <script src="/assets/body.js"></script>
+ <script src="/assets/tail.js"></script>
</ruby>
h5. register_stylesheet_expansion
-Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +public/stylesheets+.
+Register one or more stylesheet files to be included when symbol is passed to +stylesheet_link_tag+. This method is typically intended to be called from plugin initialization to register stylesheet files that the plugin installed in +vendor/assets/stylesheets+.
<ruby>
ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
stylesheet_link_tag :monkey # =>
- <link href="/stylesheets/head.css" media="screen" rel="stylesheet" />
- <link href="/stylesheets/body.css" media="screen" rel="stylesheet" />
- <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" />
+ <link href="/assets/head.css" media="screen" rel="stylesheet" />
+ <link href="/assets/body.css" media="screen" rel="stylesheet" />
+ <link href="/assets/tail.css" media="screen" rel="stylesheet" />
</ruby>
h5. auto_discovery_link_tag
@@ -577,44 +577,49 @@ auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS
h5. image_path
-Computes the path to an image asset in the +public/images+ directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path.
+Computes the path to an image asset in the +app/assets/images+ directory. Full paths from the document root will be passed through. Used internally by +image_tag+ to build the image path.
<ruby>
-image_path("edit.png") # => /images/edit.png
+image_path("edit.png") # => /assets/edit.png
+</ruby>
+
+Fingerprint will be added to the filename if config.assets.digest is set to true.
+
+<ruby>
+image_path("edit.png") # => /assets/edit-2d1a2db63fc738690021fedb5a65b68e.png
</ruby>
h5. image_url
-Computes the url to an image asset in the +public/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host.
+Computes the url to an image asset in the +app/asset/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host.
<ruby>
-image_url("edit.png") # => http://www.example.com/images/edit.png
+image_url("edit.png") # => http://www.example.com/assets/edit.png
</ruby>
h5. image_tag
-Returns an html image tag for the source. The source can be a full path or a file that exists in your +public/images+ directory.
+Returns an html image tag for the source. The source can be a full path or a file that exists in your +app/assets/images+ directory.
<ruby>
-image_tag("icon.png") # => <img src="/images/icon.png" alt="Icon" />
+image_tag("icon.png") # => <img src="/assets/icon.png" alt="Icon" />
</ruby>
h5. javascript_include_tag
-Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of JavaScript files that exist in your +public/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root.
+Returns an html script tag for each of the sources provided. You can pass in the filename (+.js+ extension is optional) of JavaScript files that exist in your +app/assets/javascripts+ directory for inclusion into the current page or you can pass the full path relative to your document root.
<ruby>
-javascript_include_tag "common" # =>
- <script src="/javascripts/common.js"></script>
+javascript_include_tag "common" # => <script src="/assets/common.js"></script>
</ruby>
-If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well.
+If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +app/assets/javascripts+ directory, it will be included as well.
<ruby>
javascript_include_tag :defaults
</ruby>
-You can also include all JavaScript files in the +public/javascripts+ directory using +:all+ as the source.
+You can also include all JavaScript files in the +app/assets/javascripts+ directory using +:all+ as the source.
<ruby>
javascript_include_tag :all
@@ -629,18 +634,18 @@ javascript_include_tag :all, :cache => true # =>
h5. javascript_path
-Computes the path to a JavaScript asset in the +public/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path.
+Computes the path to a JavaScript asset in the +app/assets/javascripts+ directory. If the source filename has no extension, +.js+ will be appended. Full paths from the document root will be passed through. Used internally by +javascript_include_tag+ to build the script path.
<ruby>
-javascript_path "common" # => /javascripts/common.js
+javascript_path "common" # => /assets/common.js
</ruby>
h5. javascript_url
-Computes the url to a JavaScript asset in the +public/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host.
+Computes the url to a JavaScript asset in the +app/assets/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host.
<ruby>
-javascript_url "common" # => http://www.example.com/javascripts/common.js
+javascript_url "common" # => http://www.example.com/assets/common.js
</ruby>
h5. stylesheet_link_tag
@@ -648,8 +653,7 @@ h5. stylesheet_link_tag
Returns a stylesheet link tag for the sources specified as arguments. If you don't specify an extension, +.css+ will be appended automatically.
<ruby>
-stylesheet_link_tag "application" # =>
- <link href="/stylesheets/application.css" media="screen" rel="stylesheet" />
+stylesheet_link_tag "application" # => <link href="/assets/application.css" media="screen" rel="stylesheet" />
</ruby>
You can also include all styles in the stylesheet directory using :all as the source:
@@ -662,23 +666,23 @@ You can also cache multiple stylesheets into one file, which requires less HTTP
<ruby>
stylesheet_link_tag :all, :cache => true
- <link href="/stylesheets/all.css" media="screen" rel="stylesheet" />
+# => <link href="/assets/all.css" media="screen" rel="stylesheet" />
</ruby>
h5. stylesheet_path
-Computes the path to a stylesheet asset in the +public/stylesheets+ directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
+Computes the path to a stylesheet asset in the +app/assets/stylesheets+ directory. If the source filename has no extension, .css will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
<ruby>
-stylesheet_path "application" # => /stylesheets/application.css
+stylesheet_path "application" # => /assets/application.css
</ruby>
h5. stylesheet_url
-Computes the url to a stylesheet asset in the +public/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host.
+Computes the url to a stylesheet asset in the +app/assets/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host.
<ruby>
-stylesheet_url "application" # => http://www.example.com/stylesheets/application.css
+stylesheet_url "application" # => http://www.example.com/assets/application.css
</ruby>
h4. AtomFeedHelper
diff --git a/guides/source/active_model_basics.textile b/guides/source/active_model_basics.textile
index d373f4ac85..7cafff2ad8 100644
--- a/guides/source/active_model_basics.textile
+++ b/guides/source/active_model_basics.textile
@@ -187,7 +187,7 @@ class Person
attr_accessor :name, :email, :token
validates :name, :presence => true
- validates_format_of :email, :with => /^([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})$/i
+ validates_format_of :email, :with => /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
validates! :token, :presence => true
end
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index 294ef25b33..4b14671efc 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -259,6 +259,54 @@ SELECT * FROM clients WHERE (clients.id IN (1,10))
WARNING: <tt>Model.find(array_of_primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception unless a matching record is found for <strong>all</strong> of the supplied primary keys.
+h5. take
+
+<tt>Model.take(limit)</tt> retrieves the first number of records specified by +limit+ without any explicit ordering:
+
+<ruby>
+Client.take(2)
+# => [#<Client id: 1, first_name: "Lifo">,
+ #<Client id: 2, first_name: "Raf">]
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 2
+</sql>
+
+h5. first
+
+<tt>Model.first(limit)</tt> finds the first number of records specified by +limit+ ordered by primary key:
+
+<ruby>
+Client.first(2)
+# => [#<Client id: 1, first_name: "Lifo">,
+ #<Client id: 2, first_name: "Raf">]
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 2
+</sql>
+
+h5. last
+
+<tt>Model.last(limit)</tt> finds the number of records specified by +limit+ ordered by primary key in descending order:
+
+<ruby>
+Client.last(2)
+# => [#<Client id: 10, first_name: "Ryan">,
+ #<Client id: 9, first_name: "John">]
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients ORDER By id DESC LIMIT 2
+</sql>
+
h4. Retrieving Multiple Objects in Batches
We often need to iterate over a large set of records, as when we send a newsletter to a large set of users, or when we export data.
diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile
index 80faffa49c..99b8bdd3fd 100644
--- a/guides/source/active_support_core_extensions.textile
+++ b/guides/source/active_support_core_extensions.textile
@@ -156,7 +156,7 @@ NOTE: Defined in +active_support/core_ext/object/duplicable.rb+.
h4. +deep_dup+
-The +deep_dup+ method returns deep copy of given object. Normally, when you +dup+ an object that contains other objects, ruby does not +dup+ them. If you have array with a string, for example, it will look like this:
+The +deep_dup+ method returns deep copy of a given object. Normally, when you +dup+ an object that contains other objects, ruby does not +dup+ them. If you have an array with a string, for example, it will look like this:
<ruby>
array = ['string']
@@ -164,7 +164,7 @@ duplicate = array.dup
duplicate.push 'another-string'
-# object was duplicated, element added only to duplicate
+# object was duplicated, so element was added only to duplicate
array #=> ['string']
duplicate #=> ['string', 'another-string']
@@ -177,7 +177,7 @@ duplicate #=> ['foo', 'another-string']
As you can see, after duplicating +Array+ instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since +dup+ does not make deep copy, the string inside array is still the same object.
-If you need a deep copy of an object, you should use +deep_dup+ in such situation:
+If you need a deep copy of an object, you should use +deep_dup+. Here is an example:
<ruby>
array = ['string']
@@ -189,7 +189,7 @@ array #=> ['string']
duplicate #=> ['foo']
</ruby>
-If object is not duplicable +deep_dup+ will just return this object:
+If object is not duplicable, +deep_dup+ will just return this object:
<ruby>
number = 1
@@ -201,9 +201,21 @@ NOTE: Defined in +active_support/core_ext/object/deep_dup.rb+.
h4. +try+
-Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first. +try+ is like +Object#send+ except that it returns +nil+ if sent to +nil+.
+When you want to call a method on an object only if it is not +nil+, the simplest way to achieve it is with conditional statements, adding unnecessary clutter. The alternative is to use +try+. +try+ is like +Object#send+ except that it returns +nil+ if sent to +nil+.
-For instance, in this code from +ActiveRecord::ConnectionAdapters::AbstractAdapter+ +@logger+ could be +nil+, but you save the check and write in an optimistic style:
+Here is an example:
+
+<ruby>
+# without try
+unless @number.nil?
+ @number.next
+end
+
+# with try
+@number.try(:next)
+</ruby>
+
+Another example is this code from +ActiveRecord::ConnectionAdapters::AbstractAdapter+ where +@logger+ could be +nil+. You can see that the code uses +try+ and avoids an unnecessary check.
<ruby>
def log_info(sql, name, ms)
@@ -245,7 +257,7 @@ NOTE: Defined in +active_support/core_ext/kernel/singleton_class.rb+.
h4. +acts_like?(duck)+
-The method +acts_like+ provides a way to check whether some class acts like some other class based on a simple convention: a class that provides the same interface as +String+ defines
+The method +acts_like?+ provides a way to check whether some class acts like some other class based on a simple convention: a class that provides the same interface as +String+ defines
<ruby>
def acts_like_string?
@@ -1840,6 +1852,93 @@ date and time arithmetic.
NOTE: Defined in +active_support/core_ext/numeric/time.rb+.
+h4. Formatting
+
+Enables the formatting of numbers in a variety of ways.
+
+Produce a string representation of a number as a telephone number:
+
+<ruby>
+5551234.to_s(:phone)
+# => 555-1234
+1235551234.to_s(:phone)
+# => 123-555-1234
+1235551234.to_s(:phone, :area_code => true)
+# => (123) 555-1234
+1235551234.to_s(:phone, :delimiter => " ")
+# => 123 555 1234
+1235551234.to_s(:phone, :area_code => true, :extension => 555)
+# => (123) 555-1234 x 555
+1235551234.to_s(:phone, :country_code => 1)
+# => +1-123-555-1234
+</ruby>
+
+Produce a string representation of a number as currency:
+
+<ruby>
+1234567890.50.to_s(:currency) # => $1,234,567,890.50
+1234567890.506.to_s(:currency) # => $1,234,567,890.51
+1234567890.506.to_s(:currency, :precision => 3) # => $1,234,567,890.506
+</ruby>
+
+Produce a string representation of a number as a percentage:
+
+<ruby>
+100.to_s(:percentage)
+# => 100.000%
+100.to_s(:percentage, :precision => 0)
+# => 100%
+1000.to_s(:percentage, :delimiter => '.', :separator => ',')
+# => 1.000,000%
+302.24398923423.to_s(:percentage, :precision => 5)
+# => 302.24399%
+</ruby>
+
+Produce a string representation of a number in delimited form:
+
+<ruby>
+12345678.to_s(:delimited) # => 12,345,678
+12345678.05.to_s(:delimited) # => 12,345,678.05
+12345678.to_s(:delimited, :delimiter => ".") # => 12.345.678
+12345678.to_s(:delimited, :delimiter => ",") # => 12,345,678
+12345678.05.to_s(:delimited, :separator => " ") # => 12,345,678 05
+</ruby>
+
+Produce a string representation of a number rounded to a precision:
+
+<ruby>
+111.2345.to_s(:rounded) # => 111.235
+111.2345.to_s(:rounded, :precision => 2) # => 111.23
+13.to_s(:rounded, :precision => 5) # => 13.00000
+389.32314.to_s(:rounded, :precision => 0) # => 389
+111.2345.to_s(:rounded, :significant => true) # => 111
+</ruby>
+
+Produce a string representation of a number as a human-readable number of bytes:
+
+<ruby>
+123.to_s(:human_size) # => 123 Bytes
+1234.to_s(:human_size) # => 1.21 KB
+12345.to_s(:human_size) # => 12.1 KB
+1234567.to_s(:human_size) # => 1.18 MB
+1234567890.to_s(:human_size) # => 1.15 GB
+1234567890123.to_s(:human_size) # => 1.12 TB
+</ruby>
+
+Produce a string representation of a number in human-readable words:
+
+<ruby>
+123.to_s(:human) # => "123"
+1234.to_s(:human) # => "1.23 Thousand"
+12345.to_s(:human) # => "12.3 Thousand"
+1234567.to_s(:human) # => "1.23 Million"
+1234567890.to_s(:human) # => "1.23 Billion"
+1234567890123.to_s(:human) # => "1.23 Trillion"
+1234567890123456.to_s(:human) # => "1.23 Quadrillion"
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/numeric/formatting.rb+.
+
h3. Extensions to +Integer+
h4. +multiple_of?+
@@ -2387,6 +2486,7 @@ To do so, the method loops over the pairs and builds nodes that depend on the _v
* If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
* Otherwise, a node with +key+ as tag is created with a string representation of +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is added as well according to the following mapping:
+
<ruby>
XML_TYPE_NAMES = {
"Symbol" => "symbol",
@@ -2549,6 +2649,45 @@ There's also the bang variant +except!+ that removes keys in the very receiver.
NOTE: Defined in +active_support/core_ext/hash/except.rb+.
+h5. +transform_keys+ and +transform_keys!+
+
+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, "A" => :a, "1" => 1}
+</ruby>
+
+The result in case of collision is undefined:
+
+<ruby>
+{"a" => 1, :a => 2}.transform_keys{ |key| key.to_s.upcase }
+# => {"A" => 2}, in my test, can't rely on this result though
+</ruby>
+
+This method may be useful for example to build specialized conversions. For instance +stringify_keys+ and +symbolize_keys+ use +transform_keys+ to perform their key conversions:
+
+<ruby>
+def stringify_keys
+ transform_keys{ |key| key.to_s }
+end
+...
+def symbolize_keys
+ transform_keys{ |key| key.to_sym rescue key }
+end
+</ruby>
+
+There's also the bang variant +transform_keys!+ that applies the block operations to keys in the very receiver.
+
+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, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}}
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/hash/keys.rb+.
+
h5. +stringify_keys+ and +stringify_keys!+
The method +stringify_keys+ returns a hash that has a stringified version of the keys in the receiver. It does so by sending +to_s+ to them:
@@ -2579,6 +2718,13 @@ The second line can safely access the "type" key, and let the user to pass eithe
There's also the bang variant +stringify_keys!+ that stringifies keys in the very receiver.
+Besides that, one can use +deep_stringify_keys+ and +deep_stringify_keys!+ to stringify 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_stringify_keys
+# => {""=>nil, "1"=>1, "nested"=>{"a"=>3, "5"=>5}}
+</ruby>
+
NOTE: Defined in +active_support/core_ext/hash/keys.rb+.
h5. +symbolize_keys+ and +symbolize_keys!+
@@ -2613,6 +2759,13 @@ The second line can safely access the +:params+ key, and let the user to pass ei
There's also the bang variant +symbolize_keys!+ that symbolizes keys in the very receiver.
+Besides that, one can use +deep_symbolize_keys+ and +deep_symbolize_keys!+ to symbolize 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_symbolize_keys
+# => {nil=>nil, 1=>1, :nested=>{:a=>3, 5=>5}}
+</ruby>
+
NOTE: Defined in +active_support/core_ext/hash/keys.rb+.
h5. +to_options+ and +to_options!+
@@ -2892,6 +3045,27 @@ Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000
+prev_month+ is aliased to +last_month+.
+h6. +prev_quarter+, +next_quarter+
+
+Same as +prev_month+ and +next_month+. It returns the date with the same day in the previous or next quarter:
+
+<ruby>
+t = Time.local(2010, 5, 8) # => Sat, 08 May 2010
+t.prev_quarter # => Mon, 08 Feb 2010
+t.next_quarter # => Sun, 08 Aug 2010
+</ruby>
+
+If such a day does not exist, the last day of the corresponding month is returned:
+
+<ruby>
+Time.local(2000, 7, 31).prev_quarter # => Sun, 30 Apr 2000
+Time.local(2000, 5, 31).prev_quarter # => Tue, 29 Feb 2000
+Time.local(2000, 10, 31).prev_quarter # => Mon, 30 Oct 2000
+Time.local(2000, 11, 31).next_quarter # => Wed, 28 Feb 2001
+</ruby>
+
++prev_quarter+ is aliased to +last_quarter+.
+
h6. +beginning_of_week+, +end_of_week+
The methods +beginning_of_week+ and +end_of_week+ return the dates for the
diff --git a/guides/source/active_support_instrumentation.textile b/guides/source/active_support_instrumentation.textile
index 430549fba4..dcdd9d14f5 100644
--- a/guides/source/active_support_instrumentation.textile
+++ b/guides/source/active_support_instrumentation.textile
@@ -15,7 +15,7 @@ h3. Introduction to instrumentation
The instrumentation API provided by ActiveSupport allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in <TODO: link to section detailing each hook point>. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
-For example, there is a hook provided within Active Record that is called every time Active Record uses a SQL query on a database. This hook could be *subscribed* to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
+For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be *subscribed* to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
You are even able to create your own events inside your application which you can later subscribe to.
@@ -377,7 +377,7 @@ listen to any notification.
The block receives the following arguments:
# The name of the event
-# Time when is started
+# Time when it started
# Time when it finished
# An unique ID for this event
# The payload (described in previous sections)
diff --git a/guides/source/caching_with_rails.textile b/guides/source/caching_with_rails.textile
index 34a100cd3a..3ee36ae971 100644
--- a/guides/source/caching_with_rails.textile
+++ b/guides/source/caching_with_rails.textile
@@ -332,7 +332,7 @@ h4. ActiveSupport::Cache::MemoryStore
This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the +:size+ options to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed.
<ruby>
-config.cache_store = :memory_store, :size => 64.megabytes
+config.cache_store = :memory_store, { :size => 64.megabytes }
</ruby>
If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then your Rails server process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments, but can work well for small, low traffic sites with only a couple of server processes or for development and test environments.
diff --git a/guides/source/command_line.textile b/guides/source/command_line.textile
index b656a0857a..19e42cea93 100644
--- a/guides/source/command_line.textile
+++ b/guides/source/command_line.textile
@@ -31,20 +31,21 @@ h4. +rails new+
The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails.
-TIP: You can install the rails gem by typing +gem install rails+, if you don't have it already.
+INFO: You can install the rails gem by typing +gem install rails+, if you don't have it already.
<shell>
$ rails new commandsapp
create
create README.rdoc
- create .gitignore
create Rakefile
create config.ru
+ create .gitignore
create Gemfile
create app
...
create tmp/cache
- create tmp/pids
+ ...
+ run bundle install
</shell>
Rails will set you up with what seems like a huge amount of stuff for such a tiny command! You've got the entire Rails directory structure now with all the code you need to run our simple application right out of the box.
@@ -61,17 +62,17 @@ With no further work, +rails server+ will run our new shiny Rails app:
$ cd commandsapp
$ rails server
=> Booting WEBrick
-=> Rails 3.1.0 application starting in development on http://0.0.0.0:3000
+=> Rails 3.2.3 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
-[2010-04-18 03:20:33] INFO WEBrick 1.3.1
-[2010-04-18 03:20:33] INFO ruby 1.8.7 (2010-01-10) [x86_64-linux]
-[2010-04-18 03:20:33] INFO WEBrick::HTTPServer#start: pid=26086 port=3000
+[2012-05-28 00:39:41] INFO WEBrick 1.3.1
+[2012-05-28 00:39:41] INFO ruby 1.9.2 (2011-02-18) [x86_64-darwin11.2.0]
+[2012-05-28 00:39:41] INFO WEBrick::HTTPServer#start: pid=69680 port=3000
</shell>
With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open "http://localhost:3000":http://localhost:3000, you will see a basic Rails app running.
-You can also use the alias "s" to start the server: <tt>rails s</tt>.
+INFO: You can also use the alias "s" to start the server: <tt>rails s</tt>.
The server can be run on a different port using the +-p+ option. The default development environment can be changed using +-e+.
@@ -85,7 +86,7 @@ h4. +rails generate+
The +rails generate+ command uses templates to create a whole lot of things. Running +rails generate+ by itself gives a list of available generators:
-You can also use the alias "g" to invoke the generator command: <tt>rails g</tt>.
+INFO: You can also use the alias "g" to invoke the generator command: <tt>rails g</tt>.
<shell>
$ rails generate
@@ -97,6 +98,7 @@ Usage: rails generate GENERATOR [args] [options]
Please choose a generator below.
Rails:
+ assets
controller
generator
...
@@ -118,23 +120,22 @@ Usage: rails generate controller NAME [action action] [options]
...
...
+Description:
+ ...
+
+ To create a controller within a module, specify the controller name as a
+ path like 'parent_module/controller_name'.
+
+ ...
+
Example:
- rails generate controller CreditCard open debit credit close
+ `rails generate controller CreditCard open debit credit close`
Credit card controller with URLs like /credit_card/debit.
- Controller: app/controllers/credit_card_controller.rb
- Views: app/views/credit_card/debit.html.erb [...]
- Helper: app/helpers/credit_card_helper.rb
- Test: test/functional/credit_card_controller_test.rb
-
-Modules Example:
- rails generate controller 'admin/credit_card' suspend late_fee
-
- Credit card admin controller with URLs like /admin/credit_card/suspend.
- Controller: app/controllers/admin/credit_card_controller.rb
- Views: app/views/admin/credit_card/debit.html.erb [...]
- Helper: app/helpers/admin/credit_card_helper.rb
- Test: test/functional/admin/credit_card_controller_test.rb
+ Controller: app/controllers/credit_card_controller.rb
+ Functional Test: test/functional/credit_card_controller_test.rb
+ Views: app/views/credit_card/debit.html.erb [...]
+ Helper: app/helpers/credit_card_helper.rb
</shell>
The controller generator is expecting parameters in the form of +generate controller ControllerName action1 action2+. Let's make a +Greetings+ controller with an action of *hello*, which will say something nice to us.
@@ -153,10 +154,10 @@ $ rails generate controller Greetings hello
invoke test_unit
create test/unit/helpers/greetings_helper_test.rb
invoke assets
- create app/assets/javascripts/greetings.js
- invoke css
- create app/assets/stylesheets/greetings.css
-
+ invoke coffee
+ create app/assets/javascripts/greetings.js.coffee
+ invoke scss
+ create app/assets/stylesheets/greetings.css.scss
</shell>
What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file.
@@ -193,21 +194,19 @@ Rails comes with a generator for data models too.
<shell>
$ rails generate model
-Usage: rails generate model NAME [field:type field:type] [options]
+Usage:
+ rails generate model NAME [field[:type][:index] field[:type][:index]] [options]
...
-Examples:
- rails generate model account
-
- Model: app/models/account.rb
- Test: test/unit/account_test.rb
- Fixtures: test/fixtures/accounts.yml
- Migration: db/migrate/XXX_add_accounts.rb
+ActiveRecord options:
+ [--migration] # Indicates when to generate migration
+ # Default: true
- rails generate model post title:string body:text published:boolean
+...
- Creates a Post model with a string title, text body, and published flag.
+Description:
+ Create rails files for model generator.
</shell>
NOTE: For a list of available field types, refer to the "API documentation":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column for the column method for the +TableDefinition+ class.
@@ -218,46 +217,47 @@ We will set up a simple resource called "HighScore" that will keep track of our
<shell>
$ rails generate scaffold HighScore game:string score:integer
- exists app/models/
- exists app/controllers/
- exists app/helpers/
- create app/views/high_scores
- create app/views/layouts/
- exists test/functional/
- create test/unit/
- create app/assets/stylesheets/
- create app/views/high_scores/index.html.erb
- create app/views/high_scores/show.html.erb
- create app/views/high_scores/new.html.erb
- create app/views/high_scores/edit.html.erb
- create app/views/layouts/high_scores.html.erb
- create app/assets/stylesheets/scaffold.css.scss
- create app/controllers/high_scores_controller.rb
- create test/functional/high_scores_controller_test.rb
- create app/helpers/high_scores_helper.rb
- route resources :high_scores
-dependency model
- exists app/models/
- exists test/unit/
- create test/fixtures/
+ invoke active_record
+ create db/migrate/20120528060026_create_high_scores.rb
create app/models/high_score.rb
- create test/unit/high_score_test.rb
- create test/fixtures/high_scores.yml
- exists db/migrate
- create db/migrate/20100209025147_create_high_scores.rb
+ invoke test_unit
+ create test/unit/high_score_test.rb
+ create test/fixtures/high_scores.yml
+ route resources :high_scores
+ invoke scaffold_controller
+ create app/controllers/high_scores_controller.rb
+ invoke erb
+ create app/views/high_scores
+ create app/views/high_scores/index.html.erb
+ create app/views/high_scores/edit.html.erb
+ create app/views/high_scores/show.html.erb
+ create app/views/high_scores/new.html.erb
+ create app/views/high_scores/_form.html.erb
+ invoke test_unit
+ create test/functional/high_scores_controller_test.rb
+ invoke helper
+ create app/helpers/high_scores_helper.rb
+ invoke test_unit
+ create test/unit/helpers/high_scores_helper_test.rb
+ invoke assets
+ invoke coffee
+ create app/assets/javascripts/high_scores.js.coffee
+ invoke scss
+ create app/assets/stylesheets/high_scores.css.scss
+ invoke scss
+ create app/assets/stylesheets/scaffolds.css.scss
</shell>
The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the +high_scores+ table and fields), takes care of the route for the *resource*, and new tests for everything.
-The migration requires that we *migrate*, that is, run some Ruby code (living in that +20100209025147_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while.
+The migration requires that we *migrate*, that is, run some Ruby code (living in that +20120528060026_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while.
<shell>
$ rake db:migrate
-(in /home/foobar/commandsapp)
== CreateHighScores: migrating ===============================================
-- create_table(:high_scores)
- -> 0.0026s
-== CreateHighScores: migrated (0.0028s) ======================================
+ -> 0.0017s
+== CreateHighScores: migrated (0.0019s) ======================================
</shell>
INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment.
@@ -274,19 +274,19 @@ h4. +rails console+
The +console+ command lets you interact with your Rails application from the command line. On the underside, +rails console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website.
-You can also use the alias "c" to invoke the console: <tt>rails c</tt>.
+INFO: You can also use the alias "c" to invoke the console: <tt>rails c</tt>.
-You can specify the environment in which the +console+ command should operate using the +-e+ switch.
+You can specify the environment in which the +console+ command should operate.
<shell>
-$ rails console -e staging
+$ rails console staging
</shell>
If you wish to test out some code without changing any data, you can do that by invoking +rails console --sandbox+.
<shell>
$ rails console --sandbox
-Loading development environment in sandbox (Rails 3.1.0)
+Loading development environment in sandbox (Rails 3.2.3)
Any modifications you make will be rolled back on exit
irb(main):001:0>
</shell>
@@ -295,7 +295,7 @@ h4. +rails dbconsole+
+rails dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3.
-You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>.
+INFO: You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>.
h4. +rails runner+
@@ -305,7 +305,7 @@ h4. +rails runner+
$ rails runner "Model.long_running_method"
</shell>
-You can also use the alias "r" to invoke the runner: <tt>rails r</tt>.
+INFO: You can also use the alias "r" to invoke the runner: <tt>rails r</tt>.
You can specify the environment in which the +runner+ command should operate using the +-e+ switch.
@@ -317,31 +317,25 @@ h4. +rails destroy+
Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it.
-You can also use the alias "d" to invoke the destroy command: <tt>rails d</tt>.
+INFO: You can also use the alias "d" to invoke the destroy command: <tt>rails d</tt>.
<shell>
$ rails generate model Oops
- exists app/models/
- exists test/unit/
- exists test/fixtures/
- create app/models/oops.rb
- create test/unit/oops_test.rb
- create test/fixtures/oops.yml
- exists db/migrate
- create db/migrate/20081221040817_create_oops.rb
+ invoke active_record
+ create db/migrate/20120528062523_create_oops.rb
+ create app/models/oops.rb
+ invoke test_unit
+ create test/unit/oops_test.rb
+ create test/fixtures/oops.yml
+</shell>
+<shell>
$ rails destroy model Oops
- notempty db/migrate
- notempty db
- rm db/migrate/20081221040817_create_oops.rb
- rm test/fixtures/oops.yml
- rm test/unit/oops_test.rb
- rm app/models/oops.rb
- notempty test/fixtures
- notempty test
- notempty test/unit
- notempty test
- notempty app/models
- notempty app
+ invoke active_record
+ remove db/migrate/20120528062523_create_oops.rb
+ remove app/models/oops.rb
+ invoke test_unit
+ remove test/unit/oops_test.rb
+ remove test/fixtures/oops.yml
</shell>
h3. Rake
@@ -352,16 +346,16 @@ You can get a list of Rake tasks available to you, which will often depend on yo
<shell>
$ rake --tasks
-(in /home/foobar/commandsapp)
-rake db:abort_if_pending_migrations # Raises an error if there are pending migrations
-rake db:charset # Retrieves the charset for the current environment's database
-rake db:collation # Retrieves the collation for the current environment's database
-rake db:create # Create the database defined in config/database.yml for the current Rails.env
+rake about # List versions of all Rails frameworks and the environment
+rake assets:clean # Remove compiled assets
+rake assets:precompile # Compile all the assets named in config.assets.precompile
+rake db:create # Create the database from config/database.yml for the current Rails.env
...
+rake log:clear # Truncates all *.log files in log/ to zero bytes
+rake middleware # Prints out your Rack middleware stack
...
-rake tmp:pids:clear # Clears all files in tmp/pids
-rake tmp:sessions:clear # Clears all files in tmp/sessions
-rake tmp:sockets:clear # Clears all files in tmp/sockets
+rake tmp:clear # Clear session, cache, and socket files from tmp/ (narrow w/ tmp:sessions:clear, tmp:cache:clear, tmp:sockets:clear)
+rake tmp:create # Creates tmp directories for sessions, cache, sockets, and pids
</shell>
h4. +about+
diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile
index f114075cae..af46538bf5 100644
--- a/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -585,7 +585,7 @@ After loading the framework and any gems in your application, Rails turns to loa
NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down.
-TIP: If you have any ordering dependency in your initializers, you can control the load order by naming. For example, +01_critical.rb+ will be loaded before +02_normal.rb+.
+TIP: If you have any ordering dependency in your initializers, you can control the load order through naming. Initializer files are loaded in alphabetical order by their path. For example, +01_critical.rb+ will be loaded before +02_normal.rb+.
h3. Initialization events
diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index a2254a550c..b52cd6c6b6 100644
--- a/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -109,7 +109,7 @@ You can run any single test separately too:
<shell>
$ cd actionpack
-$ ruby -Itest test/template/form_helper_test.rb
+$ bundle exec ruby -Itest test/template/form_helper_test.rb
</shell>
h4. Warnings
@@ -190,6 +190,8 @@ $ rake postgresql:build_databases
NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation.
+NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator".
+
If you’re using another database, check the files under +activerecord/test/connections+ for default connection information. You can edit these files to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
You can now run the tests as you did for +sqlite3+. The tasks are respectively
@@ -317,6 +319,8 @@ Now get busy and add or edit code. You’re on your branch now, so you can write
* Include tests that fail without your code, and pass with it.
* Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution.
+TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted.
+
h4. Follow the Coding Conventions
Rails follows a simple set of coding style conventions.
@@ -412,6 +416,42 @@ Push to your remote:
$ git push mine my_new_branch
</shell>
+You might have cloned your forked repository into your machine and might want to add the original Rails repository as a remote instead, if that's the case here's what you have to do.
+
+In the directory you cloned your fork:
+
+<shell>
+$ git remote add rails git://github.com/rails/rails.git
+</shell>
+
+Download new commits and branches from the official repository:
+
+<shell>
+$ git fetch rails
+</shell>
+
+Merge the new content:
+
+<shell>
+$ git checkout master
+$ git rebase rails/master
+</shell>
+
+Update your fork:
+
+<shell>
+$ git push origin master
+</shell>
+
+If you want to update another branches:
+
+<shell>
+$ git checkout branch_name
+$ git rebase rails/branch_name
+$ git push origin branch_name
+</shell>
+
+
h4. 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.
@@ -430,6 +470,35 @@ h4. Iterate as Necessary
It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a gem.
+h4. Backporting
+
+Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a rails team member before backporting your changes to avoid wasted effort.
+
+For simple fixes, the easiest way to backport your change is to "extract a diff from your changes in master and apply them to the target branch":http://ariejan.net/2009/10/26/how-to-create-and-apply-a-patch-with-git.
+
+First make sure your changes are the only difference between your current branch and master:
+
+<shell>
+$ git log master..HEAD
+</shell>
+
+Then extract the diff:
+
+<shell>
+$ git format-patch master --stdout > ~/my_changes.patch
+</shell>
+
+Switch over to the target branch and apply your changes:
+
+<shell>
+$ git checkout -b my_backport_branch 3-2-stable
+$ git apply ~/my_changes.patch
+</shell>
+
+This works well for simple changes. However, if your changes are complicated or if the code in master has deviated significantly from your target branch, it might require more work on your part. The difficulty of a backport varies greatly from case to case, and sometimes it is simply not worth the effort.
+
+Once you have resolved all conflicts and made sure all the tests are passing, push your changes and open a separate pull request for your backport. It is also worth noting that older branches might have a different set of build targets than master. When possible, it is best to first test your backport locally against the ruby versions listed in +.travis.yml+ before submitting your pull request.
+
And then ... think about your next contribution!
h3. Rails Contributors
diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb
index da6bd6acdf..04deec6a11 100644
--- a/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
@@ -31,6 +31,10 @@ Ruby on Rails Guides: Credits
Ryan Bigg works as a consultant at <a href="http://rubyx.com">RubyX</a> and has been working with Rails since 2006. He's co-authoring a book called <a href="http://manning.com/katz">Rails 3 in Action</a> and he's written many gems which can be seen on <a href="http://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>.
<% end %>
+<%= author('Oscar Del Ben', 'oscardelben', 'oscardelben.jpg') do %>
+Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wildfire</a>. He's a regular open source contributor (<a href="https://github.com/oscardelben">Github account</a>) and tweets regularly at <a href="https://twitter.com/oscardelben">@oscardelben</a>.
+ <% end %>
+
<%= author('Frederick Cheung', 'fcheung') do %>
Frederick Cheung is Chief Wizard at Texperts where he has been using Rails since 2006. He is based in Cambridge (UK) and when not consuming fine ales he blogs at <a href="http://www.spacevatican.org">spacevatican.org</a>.
<% end %>
diff --git a/guides/source/debugging_rails_applications.textile b/guides/source/debugging_rails_applications.textile
index 45fa4ada78..0802a2db26 100644
--- a/guides/source/debugging_rails_applications.textile
+++ b/guides/source/debugging_rails_applications.textile
@@ -698,7 +698,7 @@ There are some Rails plugins to help you to find errors and debug your applicati
h3. References
-* "ruby-debug Homepage":http://www.datanoise.com/ruby-debug
+* "ruby-debug Homepage":http://bashdb.sourceforge.net/ruby-debug/home-page.html
* "debugger Homepage":http://github.com/cldwalker/debugger
* "Article: Debugging a Rails application with ruby-debug":http://www.sitepoint.com/article/debug-rails-app-ruby-debug/
* "ruby-debug Basics screencast":http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/
diff --git a/guides/source/engines.textile b/guides/source/engines.textile
index c35305a822..86e7254201 100644
--- a/guides/source/engines.textile
+++ b/guides/source/engines.textile
@@ -36,6 +36,12 @@ To generate an engine with Rails 3.1, you will need to run the plugin generator
$ rails plugin new blorgh --full --mountable
</shell>
+The full list of options for the plugin generator may be seen by typing:
+
+<shell>
+$ rails plugin --help
+</shell>
+
The +--full+ option tells the plugin generator that you want to create an engine (which is a mountable plugin, hence the option name), creating the basic directory structure of an engine by providing things such as the foundations of an +app+ folder, as well a +config/routes.rb+ file. This generator also provides a file at +lib/blorgh/engine.rb+ which is identical in function to an application's +config/application.rb+ file.
The +--mountable+ option tells the generator to mount the engine inside the dummy testing application located at +test/dummy+ inside the engine. It does this by placing this line in to the dummy application's +config/routes.rb+ file, located at +test/dummy/config/routes.rb+ inside the engine:
diff --git a/guides/source/form_helpers.textile b/guides/source/form_helpers.textile
index 033b33ec3b..1851aceff8 100644
--- a/guides/source/form_helpers.textile
+++ b/guides/source/form_helpers.textile
@@ -150,7 +150,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with
h4. Other Helpers of Interest
-Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, URL fields and email fields:
+Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, color fields, datetime fields, datetime-local fields, month fields, week fields, URL fields and email fields:
<erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
@@ -159,8 +159,13 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
<%= date_field(:user, :born_on) %>
+<%= datetime_field(:user, :meeting_time) %>
+<%= datetime_local_field(:user, :graduation_day) %>
+<%= month_field(:user, :birthday_month) %>
+<%= week_field(:user, :birthday_week) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
+<%= color_field(:user, :favorite_color) %>
<%= time_field(:task, :started_at) %>
</erb>
@@ -173,14 +178,19 @@ Output:
<input id="user_name" name="user[name]" type="search" />
<input id="user_phone" name="user[phone]" type="tel" />
<input id="user_born_on" name="user[born_on]" type="date" />
+<input id="user_meeting_time" name="user[meeting_time]" type="datetime" />
+<input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" />
+<input id="user_birthday_month" name="user[birthday_month]" type="month" />
+<input id="user_birthday_week" name="user[birthday_week]" type="week" />
<input id="user_homepage" name="user[homepage]" type="url" />
<input id="user_address" name="user[address]" type="email" />
+<input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" />
<input id="task_started_at" name="task[started_at]" type="time" />
</html>
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
-IMPORTANT: The search, telephone, date, time, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
+IMPORTANT: The search, telephone, date, time, color, datetime, datetime-local, month, week, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.
@@ -409,6 +419,18 @@ TIP: The second argument to +options_for_select+ must be exactly equal to the de
WARNING: when +:inlude_blank+ or +:prompt:+ are not present, +:include_blank+ is forced true if the select attribute +required+ is true, display +size+ is one and +multiple+ is not true.
+You can add arbitrary attributes to the options using hashes:
+
+<erb>
+<%= options_for_select([['Lisbon', 1, :'data-size' => '2.8 million'], ['Madrid', 2, :'data-size' => '3.2 million']], 2) %>
+
+output:
+
+<option value="1" data-size="2.8 million">Lisbon</option>
+<option value="2" selected="selected" data-size="3.2 million">Madrid</option>
+...
+</erb>
+
h4. Select Boxes for Dealing with Models
In most cases form controls will be tied to a specific database model and as you might expect Rails provides helpers tailored for that purpose. Consistent with other form helpers, when dealing with models you drop the +_tag+ suffix from +select_tag+:
diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile
index e25dac22da..f25e0c0200 100644
--- a/guides/source/getting_started.textile
+++ b/guides/source/getting_started.textile
@@ -13,8 +13,6 @@ endprologue.
WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not
work in earlier versions of Rails.
-WARNING: The Edge version of this guide is currently being re-worked. Please excuse us while we re-arrange the place.
-
h3. Guide Assumptions
This guide is designed for beginners who want to get started with a Rails
@@ -77,7 +75,7 @@ By following along with this guide, you'll create a Rails project called
(very) simple weblog. Before you can start building the application, you need to
make sure that you have Rails itself installed.
-TIP: The examples below use # and $ to denote terminal prompts. If you are using Windows, your prompt will look something like c:\source_code>
+TIP: The examples below use # and $ to denote superuser and regular user terminal prompts respectively in a UNIX-like OS. If you are using Windows, your prompt will look something like c:\source_code>
h4. Installing Rails
@@ -110,7 +108,7 @@ To use this generator, open a terminal, navigate to a directory where you have r
$ rails new blog
</shell>
-This will create a Rails application called Blog in a directory called 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+.
TIP: You can see all of the command line options that the Rails
application builder accepts by running +rails new -h+.
@@ -140,7 +138,7 @@ application. Most of the work in this tutorial will happen in the +app/+ folder,
|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
|script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html|
-|tmp/|Temporary files|
+|tmp/|Temporary files (like cache, pid and session files)|
|vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems and the Rails source code (if you optionally install it into your project).|
h3. Hello, Rails!
@@ -179,7 +177,28 @@ To create a new controller, you will need to run the "controller" generator and
$ rails generate controller welcome index
</shell>
-Rails will create several files for you. 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+.
+Rails will create several files and a route for you.
+
+<shell>
+create app/controllers/welcome_controller.rb
+ route get "welcome/index"
+invoke erb
+create app/views/welcome
+create app/views/welcome/index.html.erb
+invoke test_unit
+create test/functional/welcome_controller_test.rb
+invoke helper
+create app/helpers/welcome_helper.rb
+invoke test_unit
+create test/unit/helpers/welcome_helper_test.rb
+invoke assets
+invoke coffee
+create app/assets/javascripts/welcome.js.coffee
+invoke scss
+create app/assets/stylesheets/welcome.css.scss
+</shell>
+
+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 and edit it to contain a single line of code:
@@ -197,18 +216,27 @@ You need to do this because Rails will serve any static file in the +public+ dir
Next, you have to tell Rails where your actual home page is located.
-Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+ and uncomment it. It should look something like the following:
+Open the file +config/routes.rb+ in your editor.
<ruby>
Blog::Application.routes.draw do
-
- #...
+ get "welcome/index"
+
+ # The priority is based upon order of creation:
+ # first created -> highest priority.
+ # ...
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
- root :to => "welcome#index"
+ # root :to => "welcome#index"
+</ruby>
+
+This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+ and uncomment it. It should look something like the following:
+
+<ruby>
+root :to => "welcome#index"
</ruby>
-The +root :to => "welcome#index"+ tells Rails to map requests to the root of the application to the welcome controller's index action. This was created earlier when you ran the controller generator (+rails generate controller welcome index+).
+The +root :to => "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":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":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.
@@ -502,7 +530,7 @@ database columns. In the first line we do just that (remember that
+params[:post]+ contains the attributes we're interested in). Then,
+@post.save+ is responsible for saving the model in the database.
Finally, we redirect the user to the +show+ action,
-wich we'll define later.
+which we'll define later.
TIP: As we'll see later, +@post.save+ returns a boolean indicating
wherever the model was saved or not.
@@ -612,7 +640,7 @@ 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 +&lt;table&gt;+ tag:
<erb>
<%= link_to 'New post', :action => :new %>
@@ -1131,7 +1159,7 @@ together.
Here we're using +link_to+ in a different way. We wrap the
+:action+ and +:id+ attributes in a hash so that we can pass those two keys in
first as one argument, and then the final two keys as another argument. The +:method+ and +:confirm+
-options are used as html5 attributes so that when the click is linked,
+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
diff --git a/guides/source/initialization.textile b/guides/source/initialization.textile
index 155a439e64..0638bbed10 100644
--- a/guides/source/initialization.textile
+++ b/guides/source/initialization.textile
@@ -1,19 +1,21 @@
h2. The Rails Initialization Process
-This guide explains the internals of the initialization process in Rails as of Rails 3.1. It is an extremely in-depth guide and recommended for advanced Rails developers.
+This guide explains the internals of the initialization process in Rails
+as of Rails 4. It is an extremely in-depth guide and recommended for advanced Rails developers.
* Using +rails server+
* Using Passenger
endprologue.
-This guide goes through every single file, class and method call that is required to boot up the Ruby on Rails stack for a default Rails 3.1 application, explaining each part in detail along the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application.
+This guide goes through every method call that is
+required to boot up the Ruby on Rails stack for a default Rails 4 application, explaining each part in detail along the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application.
NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified.
h3. Launch!
-As of Rails 3, +script/server+ has become +rails server+. This was done to centralize all rails related commands to one common file.
+A Rails application is usually started with the command +rails server+.
h4. +bin/rails+
@@ -22,16 +24,15 @@ The actual +rails+ command is kept in _bin/rails_:
<ruby>
#!/usr/bin/env ruby
-begin
- require "rails/cli"
-rescue LoadError
- railties_path = File.expand_path('../../railties/lib', __FILE__)
+if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git'))
+ railties_path = File.expand_path('../../lib', __FILE__)
$:.unshift(railties_path)
- require "rails/cli"
end
+require "rails/cli"
</ruby>
-This file will attempt to load +rails/cli+. If it cannot find it then +railties/lib+ is added to the load path (+$:+) before retrying.
+This file will first attempt to push the +railties/lib+ directory if
+present, and then require +rails/cli+.
h4. +railties/lib/rails/cli.rb+
@@ -46,7 +47,7 @@ require 'rails/script_rails_loader'
Rails::ScriptRailsLoader.exec_script_rails!
require 'rails/ruby_version_check'
-Signal.trap("INT") { puts; exit }
+Signal.trap("INT") { puts; exit(1) }
if ARGV.first == 'plugin'
ARGV.shift
@@ -120,6 +121,9 @@ exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
This is effectively the same as running +ruby script/rails [arguments]+, where +[arguments]+ at this point in time is simply "server".
+TIP: If you execute +script/rails+ directly from your Rails app you will
+avoid executing the code that we just described.
+
h4. +script/rails+
This file is as follows:
@@ -134,30 +138,30 @@ The +APP_PATH+ constant will be used later in +rails/commands+. The +config/boot
h4. +config/boot.rb+
-+config/boot.rb+ contains this:
++config/boot.rb+ contains:
<ruby>
# Set up gems listed in the Gemfile.
-gemfile = File.expand_path('../../Gemfile', __FILE__)
-begin
- ENV['BUNDLE_GEMFILE'] = gemfile
- require 'bundler'
- Bundler.setup
-rescue Bundler::GemNotFound => e
- STDERR.puts e.message
- STDERR.puts "Try running `bundle install`."
- exit!
-end if File.exist?(gemfile)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
</ruby>
-In a standard Rails application, there's a +Gemfile+ which declares all dependencies of the application. +config/boot.rb+ sets +ENV["BUNDLE_GEMFILE"]+ to the location of this file, then requires Bundler and calls +Bundler.setup+ which adds the dependencies of the application (including all the Rails parts) to the load path, making them available for the application to load. The gems that a Rails 3.1 application depends on are as follows:
+In a standard Rails application, there's a +Gemfile+ which declares all
+dependencies of the application. +config/boot.rb+ sets
++ENV['BUNDLE_GEMFILE']+ to the location of this file. If the Gemfile
+exists, +bundler/setup+ is then required.
+
+The gems that a Rails 4 application depends on are as follows:
+
+TODO: change these when the Rails 4 release is near.
* abstract (1.0.0)
-* actionmailer (3.1.0.beta)
-* actionpack (3.1.0.beta)
-* activemodel (3.1.0.beta)
-* activerecord (3.1.0.beta)
-* activesupport (3.1.0.beta)
+* actionmailer (4.0.0.beta)
+* actionpack (4.0.0.beta)
+* activemodel (4.0.0.beta)
+* activerecord (4.0.0.beta)
+* activesupport (4.0.0.beta)
* arel (2.0.7)
* builder (3.0.0)
* bundler (1.0.6)
@@ -170,8 +174,8 @@ In a standard Rails application, there's a +Gemfile+ which declares all dependen
* rack-cache (0.5.3)
* rack-mount (0.6.13)
* rack-test (0.5.6)
-* rails (3.1.0.beta)
-* railties (3.1.0.beta)
+* rails (4.0.0.beta)
+* railties (4.0.0.beta)
* rake (0.8.7)
* sqlite3-ruby (1.3.2)
* thor (0.14.6)
@@ -183,8 +187,11 @@ h4. +rails/commands.rb+
Once +config/boot.rb+ has finished, the next file that is required is +rails/commands+ which will execute a command based on the arguments passed in. In this case, the +ARGV+ array simply contains +server+ which is extracted into the +command+ variable using these lines:
<ruby>
+ARGV << '--help' if ARGV.empty?
+
aliases = {
"g" => "generate",
+ "d" => "destroy",
"c" => "console",
"s" => "server",
"db" => "dbconsole",
@@ -195,6 +202,9 @@ command = ARGV.shift
command = aliases[command] || command
</ruby>
+TIP: As you can see, an empty ARGV list will make Rails show the help
+snippet.
+
If we used <tt>s</tt> rather than +server+, Rails will use the +aliases+ defined in the file and match them to their respective commands. With the +server+ command, Rails will run this code:
<ruby>
@@ -214,47 +224,27 @@ when 'server'
}
</ruby>
-This file will change into the root of the directory (a path two directories back from +APP_PATH+ which points at +config/application.rb+), but only if the +config.ru+ file isn't found. This then requires +rails/commands/server+ which requires +action_dispatch+ and sets up the +Rails::Server+ class.
-
-h4. +actionpack/lib/action_dispatch.rb+
-
-Action Dispatch is the routing component of the Rails framework. It depends on Active Support, +actionpack/lib/action_pack.rb+ and +Rack+ being available. The first thing required here is +active_support+.
-
-h4. +activesupport/lib/active_support.rb+
-
-This file begins with requiring +active_support/lib/active_support/dependencies/autoload.rb+ which redefines Ruby's +autoload+ method to have a little more extra behaviour especially in regards to eager autoloading. Eager autoloading is the loading of all required classes and will happen when the +config.cache_classes+ setting is +true+. The required file also requires another file: +active_support/lazy_load_hooks+
-
-h4. +activesupport/lib/active_support/lazy_load_hooks.rb+
-
-This file defines the +ActiveSupport.on_load+ hook which is used to execute code when specific parts are loaded. We'll see this in use a little later on.
-
-This file begins with requiring +active_support/inflector/methods+.
-
-h4. +activesupport/lib/active_support/inflector/methods.rb+
-
-The +methods.rb+ file is responsible for defining methods such as +camelize+, +underscore+ and +dasherize+ as well as a slew of others. The "+ActiveSupport::Inflector+ documentation":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html covers them all pretty decently.
-
-In this file there are a lot of lines such as this inside the +ActiveSupport+ module:
+This file will change into the root of the directory (a path two directories back from +APP_PATH+ which points at +config/application.rb+), but only if the +config.ru+ file isn't found. This then requires +rails/commands/server+ which sets up the +Rails::Server+ class.
<ruby>
-autoload :Inflector
-</ruby>
-
-Due to the overriding of the +autoload+ method, Ruby will know how to look for this file at +activesupport/lib/active_support/inflector.rb+ when the +Inflector+ class is first referenced.
-
-The +active_support/lib/active_support/version.rb+ that is also required here simply defines an +ActiveSupport::VERSION+ constant which defines a couple of constants inside this module, the main constant of this is +ActiveSupport::VERSION::STRING+ which returns the current version of ActiveSupport.
-
-The +active_support/lib/active_support.rb+ file simply defines the +ActiveSupport+ module and some autoloads (eager and of the normal variety) for it.
+require 'fileutils'
+require 'optparse'
+require 'action_dispatch'
-h4. +actionpack/lib/action_dispatch.rb+ cont'd.
+module Rails
+ class Server < ::Rack::Server
+</ruby>
-Now back to +action_pack/lib/action_dispatch.rb+. The next +require+ in this file is one for +action_pack+, which simply calls +action_pack/version.rb+ which defines +ActionPack::VERSION+ and the constants, much like +ActiveSpport+ does.
++fileutils+ and +optparse+ are standard Ruby libraries which provide helper functions for working with files and parsing options.
-After this line, there's a require to +active_model+ which simply defines autoloads for the +ActiveModel+ part of Rails and sets up the +ActiveModel+ module which is used later on.
+h4. +actionpack/lib/action_dispatch.rb+
-The last of the requires is to +rack+, which like the +active_model+ and +active_support+ requires before it, sets up the +Rack+ module as well as the autoloads for constants within it.
+Action Dispatch is the routing component of the Rails framework. Other
+than the rouing itself, it adds
+functionalities like routing, session, and common middlewares.
-Finally in +action_dispatch.rb+ the +ActionDispatch+ module and *its* autoloads are declared.
+Action Dispatch itself is also responsible for loading Active Support, Action
+Pack, Active Model, and Rack.
h4. +rails/commands/server.rb+
@@ -353,16 +343,27 @@ def parse!(args)
...
</ruby>
-This method will set up keys for the +options+ which Rails will then be able to use to determine how its server should run. After +initialize+ has finished, then the +start+ method will launch the server.
+This method will set up keys for the +options+ which Rails will then be
+able to use to determine how its server should run. After +initialize+
+has finished, we jump back into +rails/server+ where +APP_PATH+ (which was
+set earlier) is required.
+
+h4. +config/application+
+
+When +require APP_PATH+ is executed, +config/application.rb+ is loaded.
+This is a file exists in your app and it's free for you to change based
+on your needs. Among other things, inside this file you load gems with
+bundler, and create your application namespace.
h4. +Rails::Server#start+
-This method is defined like this:
+After +congif/application+ is loaded, +server.start+ is called. This method is defined like this:
<ruby>
def start
+ url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
- puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}"
+ puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Call with -d to detach" unless options[:daemonize]
trap(:INT) { exit }
puts "=> Ctrl-C to shutdown server" unless options[:daemonize]
@@ -372,6 +373,15 @@ def start
FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
end
+ unless options[:daemonize]
+ wrapped_app # touch the app so the logger is set up
+
+ console = ActiveSupport::Logger.new($stdout)
+ console.formatter = Rails.logger.formatter
+
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ end
+
super
ensure
# The '-h' option calls exit before @options is set.
@@ -380,10 +390,18 @@ ensure
end
</ruby>
-This is where the first output of the Rails initialization happens. This method creates a trap for +INT+ signals, so if you +CTRL+C+ the server, it will exit the process. As we can see from the code here, it will create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ directories if they don't already exist prior to calling +super+. The +super+ method will call +Rack::Server.start+ which begins its definition like this:
+This is where the first output of the Rails initialization happens. This
+method creates a trap for +INT+ signals, so if you +CTRL-C+ the server,
+it will exit the process. As we can see from the code here, it will
+create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+
+directories. It then calls +wrapped_app+ which is responsible for
+creating the Rack app, before creating and assigning an
+instance of +ActiveSupport::Logger+.
+
+The +super+ method will call +Rack::Server.start+ which begins its definition like this:
<ruby>
-def start
+def start &blk
if options[:warn]
$-w = true
end
@@ -403,22 +421,38 @@ def start
pp wrapped_app
pp app
end
-end
-</ruby>
-In a Rails application, these options are not set at all and therefore aren't used at all. The first line of code that's executed in this method is a call to this method:
+ check_pid! if options[:pid]
-<ruby>
-wrapped_app
+ # Touch the wrapped app, so that the config.ru is loaded before
+ # daemonization (i.e. before chdir, etc).
+ wrapped_app
+
+ daemonize_app if options[:daemonize]
+
+ write_pid if options[:pid]
+
+ trap(:INT) do
+ if server.respond_to?(:shutdown)
+ server.shutdown
+ else
+ exit
+ end
+ end
+
+ server.run wrapped_app, options, &blk
+end
</ruby>
-This method calls another method:
+The interesting part for a Rails app is the last line, +server.run+. Here we encounter the +wrapped_app+ method again, which this time
+we're going to explore more (even though it was executed before, and
+thus memoized by now).
<ruby>
@wrapped_app ||= build_app app
</ruby>
-Then the +app+ method here is defined like so:
+The +app+ method here is defined like so:
<ruby>
def app
@@ -440,7 +474,7 @@ The +options[:config]+ value defaults to +config.ru+ which contains this:
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
-run YourApp::Application
+run <%= app_const %>
</ruby>
@@ -451,7 +485,7 @@ app = eval "Rack::Builder.new {( " <plus> cfgfile <plus> "\n )}.to_app",
TOPLEVEL_BINDING, config
</ruby>
-The +initialize+ method will take the block here and execute it within an instance of +Rack::Builder+. This is where the majority of the initialization process of Rails happens. The chain of events that this simple line sets off will be the focus of a large majority of this guide. The +require+ line for +config/environment.rb+ in +config.ru+ is the first to run:
+The +initialize+ method of +Rack::Builder+ will take the block here and execute it within an instance of +Rack::Builder+. This is where the majority of the initialization process of Rails happens. The +require+ line for +config/environment.rb+ in +config.ru+ is the first to run:
<ruby>
require ::File.expand_path('../config/environment', __FILE__)
@@ -489,6 +523,7 @@ require "rails"
action_controller
action_mailer
rails/test_unit
+ sprockets/rails
).each do |framework|
begin
require "#{framework}/railtie"
@@ -497,616 +532,128 @@ require "rails"
end
</ruby>
-First off the line is the +rails+ require itself.
+This is where all the Rails frameworks are loaded and thus made
+available to the application. We wont go into detail of what happens
+inside each of those frameworks, but you're encouraged to try and
+explore them on your own.
-h4. +railties/lib/rails.rb+
+For now, just keep in mind that common functionality like Rails engines,
+I18n and Rails configuration is all bein defined here.
-This file is responsible for the initial definition of the +Rails+ module and, rather than defining the autoloads like +ActiveSupport+, +ActionDispatch+ and so on, it actually defines other functionality. Such as the +root+, +env+ and +application+ methods which are extremely useful in Rails 3 applications.
+h4. Back to +config/environment.rb+
-However, before all that takes place the +rails/ruby_version_check+ file is required first.
-
-h4. +railties/lib/rails/ruby_version_check.rb+
-
-This file simply checks if the Ruby version is less than 1.8.7 or is 1.9.1 and raises an error if that is the case. Rails 3 simply will not run on earlier versions of Ruby than 1.8.7 or 1.9.1.
-
-NOTE: You should always endeavor to run the latest version of Ruby with your Rails applications. The benefits are many, including security fixes and the like, and very often there is a speed increase associated with it. The caveat is that you could have code that potentially breaks on the latest version, which should be fixed to work on the latest version rather than kept around as an excuse not to upgrade.
-
-h4. +active_support/core_ext/kernel/reporting.rb+
-
-This is the first of the many Active Support core extensions that come with Rails. This one in particular defines methods in the +Kernel+ module which is mixed in to the +Object+ class so the methods are available on +main+ and can therefore be called like this:
-
-<ruby>
-silence_warnings do
- # some code
-end
-</ruby>
-
-These methods can be used to silence STDERR responses and the +silence_stream+ allows you to also silence other streams. Additionally, this mixin allows you to suppress exceptions and capture streams. For more information see the "Silencing Warnings, Streams, and Exceptions":active_support_core_extensions.html#silencing-warnings-streams-and-exceptions section from the Active Support Core Extensions Guide.
-
-h4. +active_support/core_ext/logger.rb+
-
-The next file that is required is another Active Support core extension, this time to the +Logger+ class. This begins by defining the +around_[level]+ helpers for the +Logger+ class as well as other methods such as a +datetime_format+ getter and setter for the +formatter+ object tied to a +Logger+ object.
-
-For more information see the "Extensions to Logger":active_support_core_extensions.html#extensions-to-logger section from the Active Support Core Extensions Guide.
+When +config/application.rb+ has finished loading Rails, and defined
+your application namespace, you go back to +config/environment.rb+,
+where your application is initialized. For example, if you application was called
++Blog+, here you would find +Blog::Application.initialize!+, which is
+defined in +rails/application.rb+
h4. +railties/lib/rails/application.rb+
-The next file required by +railties/lib/rails.rb+ is +application.rb+. This file defines the +Rails::Application+ constant which the application's class defined in +config/application.rb+ in a standard Rails application depends on. Before the +Rails::Application+ class is defined however, there's some other files that get required first.
-
-The first of these is +active_support/core_ext/hash/reverse_merge+ which can be "read about in the Active Support Core Extensions guide":active_support_core_extensions.html#merging under the "Merging" section.
-
-h4. +active_support/file_update_checker.rb+
-
-The +ActiveSupport::FileUpdateChecker+ class defined within this file is responsible for checking if a file has been updated since it was last checked. This is used for monitoring the routes file for changes during development environment runs.
-
-h4. +railties/lib/rails/plugin.rb+
-
-This file defines +Rails::Plugin+ which inherits from +Rails::Engine+. Unlike +Rails::Engine+ and +Rails::Railtie+ however, this class is not designed to be inherited from. Instead, this is used simply for loading plugins from within an application and an engine.
-
-This file begins by requiring +rails/engine.rb+
-
-h4. +railties/lib/rails/engine.rb+
-
-The +rails/engine.rb+ file defines the +Rails::Engine+ class which inherits from +Rails::Railtie+. The +Rails::Engine+ class defines much of the functionality found within a standard application class such as the +routes+ and +config+ methods.
-
-The "API documentation":http://api.rubyonrails.org/classes/Rails/Engine.html for +Rails::Engine+ explains the function of this class pretty well.
-
-This file's first line requires +rails/railtie.rb+.
-
-h4. +railties/lib/rails/railtie.rb+
-
-The +rails/railtie.rb+ file is responsible for defining +Rails::Railtie+, the underlying class for all ties to Rails now. Gems that want to have their own initializers or rake tasks and hook into Rails should have a +GemName::Railtie+ class that inherits from +Rails::Railtie+.
-
-The "API documentation":http://api.rubyonrails.org/classes/Rails/Railtie.html for +Rails::Railtie+, much like +Rails::Engine+, explains this class exceptionally well.
-
-The first require in this file is +rails/initializable.rb+.
-
-h4. +railties/lib/rails/initializable.rb+
-
-Now we reach the end of this particular rabbit hole as +rails/initializable.rb+ doesn't require any more Rails files, only +tsort+ from the Ruby standard library.
-
-This file defines the +Rails::Initializable+ module which contains the +Initializer+ class, the basis for all initializers in Rails. This module also contains a +ClassMethods+ class which will be included into the +Rails::Railtie+ class when these requires have finished.
-
-Now that +rails/initializable.rb+ has finished being required from +rails/railtie.rb+, the next require is for +rails/configuration+.
-
-h4. +railties/lib/rails/configuration.rb+
-
-This file defines the +Rails::Configuration+ module, containing the +MiddlewareStackProxy+ class as well as the +Generators+ class. The +MiddlewareStackProxy+ class is used for managing the middleware stack for an application, which we'll see later on. The +Generators+ class provides the functionality used for configuring what generators an application uses through the "+config.generators+ option":configuring.html#configuring-generators.
-
-The first file required in this file is +activesupport/deprecation+.
-
-h4. +activesupport/lib/active_support/deprecation.rb+
-
-This file, and the files it requires, define the basic deprecation warning features found in Rails. This file is responsible for setting defaults in the +ActiveSupport::Deprecation+ module for the +deprecation_horizon+, +silenced+ and +debug+ values. The files that are required before this happens are:
-
-* +active_support/deprecation/behaviors+
-* +active_support/deprecation/reporting+
-* +active_support/deprecation/method_wrappers+
-* +active_support/deprecation/proxy_wrappers+
-
-h4. +activesupport/lib/active_support/deprecation/behaviors.rb+
-
-This file defines the behavior of the +ActiveSupport::Deprecation+ module, setting up the +DEFAULT_BEHAVIORS+ hash constant which contains the three defaults to outputting deprecation warnings: +:stderr+, +:log+ and +:notify+. This file begins by requiring +activesupport/notifications+ and +activesupport/core_ext/array/wrap+.
-
-h4. +activesupport/lib/active_support/notifications.rb+
-
-This file defines the +ActiveSupport::Notifications+ module. Notifications provides an instrumentation API for Ruby, shipping with a queue implementation that consumes and publish events to log subscribers in a thread.
-
-The "API documentation":http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html for +ActiveSupport::Notifications+ explains the usage of this module, including the methods that it defines.
-
-The file required in +active_support/notifications.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":active_support_core_extensions.html#method-delegation.
-
-h4. +activesupport/core_ext/array/wrap+
-
-As this file comprises of a core extension, it is covered exclusively in "the Active Support Core Extensions guide":active_support_core_extensions.html#wrapping
-
-h4. +activesupport/lib/active_support/deprecation/reporting.rb+
-
-This file is responsible for defining the +warn+ and +silence+ methods for +ActiveSupport::Deprecation+ as well as additional private methods for this module.
-
-h4. +activesupport/lib/active_support/deprecation/method_wrappers.rb+
-
-This file defines a +deprecate_methods+ which is primarily used by the +module/deprecation+ core extension required by the first line of this file. Other core extensions required by this file are the +module/aliasing+ and +array/extract_options+ files.
-
-h4. +activesupport/lib/active_support/deprecation/proxy_wrappers.rb+
-
-+proxy_wrappers.rb+ defines deprecation wrappers for methods, instance variables and constants. Previously, this was used for the +RAILS_ENV+ and +RAILS_ROOT+ constants for 3.0 but since then these constants have been removed. The deprecation message that would be raised from these would be something like:
-
-<plain>
- BadConstant is deprecated! Use GoodConstant instead.
-</plain>
-
-h4. +active_support/ordered_options+
-
-This file is the next file required from +rails/configuration.rb+ is the file that defines +ActiveSupport::OrderedOptions+ which is used for configuration options such as +config.active_support+ and the like.
-
-The next file required is +active_support/core_ext/hash/deep_dup+ which is covered in "Active Support Core Extensions guide":active_support_core_extensions.html#deep_dup
-
-The file that is required next from is +rails/paths+
-
-h4. +railties/lib/rails/paths.rb+
-
-This file defines the +Rails::Paths+ module which allows paths to be configured for a Rails application or engine. Later on in this guide when we cover Rails configuration during the initialization process we'll see this used to set up some default paths for Rails and some of them will be configured to be eager loaded.
-
-h4. +railties/lib/rails/rack.rb+
-
-The final file to be loaded by +railties/lib/rails/configuration.rb+ is +rails/rack+ which defines some simple autoloads:
+The +initialize!+ method looks like this:
<ruby>
-module Rails
- module Rack
- autoload :Debugger, "rails/rack/debugger"
- autoload :Logger, "rails/rack/logger"
- autoload :LogTailer, "rails/rack/log_tailer"
- autoload :Static, "rails/rack/static"
- end
+def initialize!(group=:default) #:nodoc:
+ raise "Application has been already initialized." if @initialized
+ run_initializers(group, self)
+ @initialized = true
+ self
end
</ruby>
-Once this file is finished loading, then the +Rails::Configuration+ class is initialized. This completes the loading of +railties/lib/rails/configuration.rb+ and now we jump back to the loading of +railties/lib/rails/railtie.rb+, where the next file loaded is +active_support/inflector+.
-
-h4. +activesupport/lib/active_support/inflector.rb+
-
-+active_support/inflector.rb+ requires a series of file which are responsible for setting up the basics for knowing how to pluralize and singularize words. These files are:
-
-<ruby>
-require 'active_support/inflector/inflections'
-require 'active_support/inflector/transliterate'
-require 'active_support/inflector/methods'
-
-require 'active_support/inflections'
-require 'active_support/core_ext/string/inflections'
-</ruby>
-
-The +active_support/inflector/methods+ file has already been required by +active_support/autoload+ and so won't be loaded again here. The +activesupport/lib/active_support/inflector/inflections.rb+ is required by +active_support/inflector/methods+.
-
-h4. +active_support/inflections+
-
-This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes".
-
-h4. +activesupport/lib/active_support/inflector/transliterate.rb+
-
-In this file is where the "+transliterate+":http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-transliterate and +parameterize+:http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize methods are defined. The documentation for both of these methods is very much worth reading.
-
-h4. Back to +railties/lib/rails/railtie.rb+
-
-Once the inflector files have been loaded, the +Rails::Railtie+ class is defined. This class includes a module called +Initializable+, which is actually +Rails::Initializable+. This module includes the +initializer+ method which is used later on for setting up initializers, amongst other methods.
-
-h4. +railties/lib/rails/initializable.rb+
-
-When the module from this file (+Rails::Initializable+) is included, it extends the class it's included into with the +ClassMethods+ module inside of it. This module defines the +initializer+ method which is used to define initializers throughout all of the railties. This file completes the loading of +railties/lib/rails/railtie.rb+. Now we go back to +rails/engine.rb+.
-
-h4. +railties/lib/rails/engine.rb+
-
-The next file required in +rails/engine.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":active_support_core_extensions.html#method-delegation.
-
-The next two files after this are Ruby standard library files: +pathname+ and +rbconfig+. The file after these is +rails/engine/railties+.
-
-h4. +railties/lib/rails/engine/railties.rb+
-
-This file defines the +Rails::Engine::Railties+ class which provides the +engines+ and +railties+ methods which are used later on for defining rake tasks and other functionality for engines and railties.
-
-h4. Back to +railties/lib/rails/engine.rb+
-
-Once +rails/engine/railties.rb+ has finished loading the +Rails::Engine+ class gets its basic functionality defined, such as the +inherited+ method which will be called when this class is inherited from.
+As you can see, you can only initialize an app once. This is also where the initializers are run.
-Once this file has finished loading we jump back to +railties/lib/rails/plugin.rb+
+TODO: review this
-h4. Back to +railties/lib/rails/plugin.rb+
-
-The next file required in this is a core extension from Active Support called +array/conversions+ which is covered in "this section":active_support_core_extensions.html#array-conversions of the Active Support Core Extensions Guide.
-
-Once that file has finished loading, the +Rails::Plugin+ class is defined.
-
-h4. Back to +railties/lib/rails/application.rb+
-
-Jumping back to +rails/application.rb+ now. This file defines the +Rails::Application+ class where the application's class inherits from. This class (and its superclasses) define the basic behaviour on the application's constant such as the +config+ method used for configuring the application.
-
-Once this file's done then we go back to the +railties/lib/rails.rb+ file, which next requires +rails/version+.
-
-h4. +railties/lib/rails/version.rb+
-
-Much like +active_support/version+, this file defines the +VERSION+ constant which has a +STRING+ constant on it which returns the current version of Rails.
-
-Once this file has finished loading we go back to +railties/lib/rails.rb+ which then requires +active_support/railtie.rb+.
-
-h4. +activesupport/lib/active_support/railtie.rb+
-
-This file requires +active_support+ and +rails+ which have already been required so these two lines are effectively ignored. The third require in this file is to +active_support/i18n_railtie.rb+.
-
-h4. +activesupport/lib/active_support/i18n_railtie.rb+
-
-This file is the first file that sets up configuration with these lines inside the class:
-
-<ruby>
-class Railtie < Rails::Railtie
- config.i18n = ActiveSupport::OrderedOptions.new
- config.i18n.railties_load_path = []
- config.i18n.load_path = []
- config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
-</ruby>
-
-By inheriting from +Rails::Railtie+ the +Rails::Railtie#inherited+ method is called:
-
-<ruby>
-def inherited(base)
- unless base.abstract_railtie?
- base.send(:include, Railtie::Configurable)
- subclasses << base
- end
-end
-</ruby>
-
-This first checks if the Railtie that's inheriting it is a component of Rails itself:
-
-<ruby>
-ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Plugin Rails::Engine Rails::Application)
-
-...
-
-def abstract_railtie?
- ABSTRACT_RAILTIES.include?(name)
-end
-</ruby>
-
-Because +I18n::Railtie+ isn't in this list, +abstract_railtie?+ returns +false+. Therefore the +Railtie::Configurable+ module is included into this class and the +subclasses+ method is called and +I18n::Railtie+ is added to this new array.
-
-<ruby>
-def subclasses
- @subclasses ||= []
-end
-</ruby>
-
-The +config+ method used at the top of +I18n::Railtie+ is defined on +Rails::Railtie+ and is defined like this:
-
-<ruby>
-def config
- @config ||= Railtie::Configuration.new
-end
-</ruby>
-
-At this point, that +Railtie::Configuration+ constant is automatically loaded which causes the +rails/railties/configuration+ file to be loaded. The line for this is this particular line in +railties/lib/rails/railtie.rb+:
-
-<ruby>
-autoload :Configuration, "rails/railtie/configuration"
-</ruby>
+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.
-h4. +railties/lib/rails/railtie/configuration.rb+
+After this is done we go back to +Rack::Server+
-This file begins with a require out to +rails/configuration+ which has already been required earlier in the process and so isn't required again.
+h4. Rack: lib/rack/server.rb
-This file defines the +Rails::Railtie::Configuration+ class which is responsible for providing a way to easily configure railties and it's the +initialize+ method here which is called by the +config+ method back in the +i18n_railtie.rb+ file. The methods on this object don't exist, and so are rescued by the +method_missing+ defined further down in +configuration.rb+:
+Last time we left when the +app+ method was being defined:
<ruby>
-def method_missing(name, *args, &blk)
- if name.to_s =~ /=$/
- @@options[$`.to_sym] = args.first
- elsif @@options.key?(name)
- @@options[name]
- else
- super
- end
-end
-</ruby>
-
-So therefore when an option is referred to it simply stores the value as the key if it's used in a setter context, or retrieves it if used in a getter context. Nothing fancy going on there.
-
-h4. Back to +activesupport/lib/active_support/i18n_railtie.rb+
-
-After the configuration method the +reloader+ method is defined, and then the first of of Railties' initializers is defined: +i18n.callbacks+.
+def app
+ @app ||= begin
+ if !::File.exist? options[:config]
+ abort "configuration #{options[:config]} not found"
+ end
-<ruby>
-initializer "i18n.callbacks" do
- ActionDispatch::Reloader.to_prepare do
- I18n::Railtie.reloader.execute_if_updated
+ app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
+ self.options.merge! options
+ app
end
end
</ruby>
-The +initializer+ method (from the +Rails::Initializable+ module) here doesn't run the block, but rather stores it to be run later on:
-
-<ruby>
-def initializer(name, opts = {}, &blk)
- raise ArgumentError, "A block must be passed when defining an initializer" unless blk
- opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
- initializers << Initializer.new(name, nil, opts, &blk)
-end
-</ruby>
-
-An initializer can be configured to run before or after another initializer, which we'll see a couple of times throughout this initialization process. Anything that inherits from +Rails::Railtie+ may also make use of the +initializer+ method, something which is covered in the "Configuration guide":configuring.html#rails-railtie-initializer.
-
-The +Initializer+ class here is defined within the +Rails::Initializable+ module and its +initialize+ method is defined to just set up a couple of variables:
+At this point +app+ is the Rails app itself (a middleware), and what
+happens next is Rack will call all the provided middlewares:
<ruby>
-def initialize(name, context, options, &block)
- @name, @context, @options, @block = name, context, options, block
-end
-</ruby>
-
-Once this +initialize+ method is finished, the object is added to the object the +initializers+ method returns:
-
-<ruby>
-def initializers
- @initializers ||= self.class.initializers_for(self)
-end
-</ruby>
-
-If +@initializers+ isn't set (which it won't be at this point), the +intializers_for+ method will be called for this class.
-
-<ruby>
-def initializers_for(binding)
- Collection.new(initializers_chain.map { |i| i.bind(binding) })
-end
-</ruby>
-
-The +Collection+ class in +railties/lib/rails/initializable.rb+ inherits from +Array+ and includes the +TSort+ module which is used to sort out the order of the initializers based on the order they are placed in.
-
-The +initializers_chain+ method referenced in the +initializers_for+ method is defined like this:
-
-<ruby>
-def initializers_chain
- initializers = Collection.new
- ancestors.reverse_each do | klass |
- next unless klass.respond_to?(:initializers)
- initializers = initializers + klass.initializers
+def build_app(app)
+ middleware[options[:environment]].reverse_each do |middleware|
+ middleware = middleware.call(self) if middleware.respond_to?(:call)
+ next unless middleware
+ klass = middleware.shift
+ app = klass.new(app, *middleware)
end
- initializers
-end
-</ruby>
-
-This method collects the initializers from the ancestors of this class and adds them to a new +Collection+ object using the <tt>+</tt> method which is defined like this for the <tt>Collection</tt> class:
-
-<ruby>
-def +(other)
- Collection.new(to_a + other.to_a)
-end
-</ruby>
-
-So this <tt>+</tt> method is overridden to return a new collection comprising of the existing collection as an array and then using the <tt>Array#+</tt> method combines these two collections, returning a "super" +Collection+ object. In this case, the only initializer that's going to be in this new +Collection+ object is the +i18n.callbacks+ initializer.
-
-The next method to be called after this +initializer+ method is the +after_initialize+ method on the +config+ object, which is defined like this:
-
-<ruby>
-def after_initialize(&block)
- ActiveSupport.on_load(:after_initialize, :yield => true, &block)
-end
-</ruby>
-
-The +on_load+ method here is provided by the +active_support/lazy_load_hooks+ file which was required earlier and is defined like this:
-
-<ruby>
-def self.on_load(name, options = {}, &block)
- if base = @loaded[name]
- execute_hook(base, options, block)
+ app
+end
+</ruby>
+
+Remember, +build_app+ was called (by wrapped_app) in the last line of +Server#start+.
+Here's how it looked like when we left:
+
+<ruby>
+server.run wrapped_app, options, &blk
+</ruby>
+
+At this point, the implementation of +server.run+ will depend on the
+server you're using. For example, if you were using Mongrel, here's what
+the +run+ method would look like:
+
+<ruby>
+def self.run(app, options={})
+ server = ::Mongrel::HttpServer.new(
+ options[:Host] || '0.0.0.0',
+ options[:Port] || 8080,
+ options[:num_processors] || 950,
+ options[:throttle] || 0,
+ options[:timeout] || 60)
+ # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
+ # Use is similar to #run, replacing the app argument with a hash of
+ # { path=>app, ... } or an instance of Rack::URLMap.
+ if options[:map]
+ if app.is_a? Hash
+ app.each do |path, appl|
+ path = '/'+path unless path[0] == ?/
+ server.register(path, Rack::Handler::Mongrel.new(appl))
+ end
+ elsif app.is_a? URLMap
+ app.instance_variable_get(:@mapping).each do |(host, path, appl)|
+ next if !host.nil? && !options[:Host].nil? && options[:Host] != host
+ path = '/'+path unless path[0] == ?/
+ server.register(path, Rack::Handler::Mongrel.new(appl))
+ end
+ else
+ raise ArgumentError, "first argument should be a Hash or URLMap"
+ end
else
- @load_hooks[name] << [block, options]
+ server.register('/', Rack::Handler::Mongrel.new(app))
end
+ yield server if block_given?
+ server.run.join
end
</ruby>
-The +@loaded+ variable here is a hash containing elements representing the different components of Rails that have been loaded at this stage. Currently, this hash is empty. So the +else+ is executed here, using the +@load_hooks+ variable defined in +active_support/lazy_load_hooks+:
-
-<ruby>
-@load_hooks = Hash.new {|h,k| h[k] = [] }
-</ruby>
-
-This defines a new hash which has keys that default to empty arrays. This saves Rails from having to do something like this instead:
-
-<ruby>
-@load_hooks[name] = []
-@load_hooks[name] << [block, options]
-</ruby>
-
-The value added to this array here consists of the block and options passed to +after_initialize+.
-
-We'll see these +@load_hooks+ used later on in the initialization process.
-
-This rest of +i18n_railtie.rb+ defines the protected class methods +include_fallback_modules+, +init_fallbacks+ and +validate_fallbacks+.
-
-h4. Back to +activesupport/lib/active_support/railtie.rb+
-
-This file defines the +ActiveSupport::Railtie+ constant which like the +I18n::Railtie+ constant just defined, inherits from +Rails::Railtie+ meaning the +inherited+ method would be called again here, including +Rails::Configurable+ into this class. This class makes use of +Rails::Railtie+'s +config+ method again, setting up the configuration options for Active Support.
-
-Then this Railtie sets up three more initializers:
-
-* +active_support.initialize_whiny_nils+
-* +active_support.deprecation_behavior+
-* +active_support.initialize_time_zone+
-
-We will cover what each of these initializers do when they run.
-
-Once the +active_support/railtie+ file has finished loading the next file required from +railties/lib/rails.rb+ is the +action_dispatch/railtie+.
-
-h4. +activesupport/lib/action_dispatch/railtie.rb+
-
-This file defines the +ActionDispatch::Railtie+ class, but not before requiring +action_dispatch+.
-
-h4. +activesupport/lib/action_dispatch.rb+
-
-This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are.
-
-<ruby>
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-</ruby>
-
-In effect, these lines only define the +activesupport_path+ and +activemodel_path+ variables and nothing more.
-
-The next two requires in this file are already done, so they are not run:
-
-<ruby>
-require 'active_support'
-require 'active_support/dependencies/autoload'
-</ruby>
-
-The following require is to +action_pack+ (+activesupport/lib/action_pack.rb+) which has a 22-line copyright notice at the top of it and ends in a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant:
-
-<ruby>
-module ActionPack
- module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
- TINY = 0
- PRE = "beta"
-
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
- end
-end
-</ruby>
-
-Once +action_pack+ is finished, then +active_model+ is required.
-
-h4. +activemodel/lib/active_model.rb+
-
-This file makes a require to +active_model/version+ which defines the version for Active Model:
-
-<ruby>
-module ActiveModel
- module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
- TINY = 0
- PRE = "beta"
-
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
- end
-end
-</ruby>
-
-Once the +version.rb+ file is loaded, the +ActiveModel+ module has its autoloaded constants defined as well as a sub-module called +ActiveModel::Serializers+ which has autoloads of its own. When the +ActiveModel+ module is closed the +active_support/i18n+ file is required.
-
-h4. +activesupport/lib/active_support/i18n.rb+
-
-This is where the +i18n+ gem is required and first configured:
-
-<ruby>
-begin
- require 'i18n'
- require 'active_support/lazy_load_hooks'
-rescue LoadError => e
- $stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install"
- raise e
-end
-
-I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
-</ruby>
-
-In effect, the +I18n+ module first defined by +i18n_railtie+ is extended by the +i18n+ gem, rather than the other way around. This has no ill effect. They both work on the same way.
-
-This is another spot where +active_support/lazy_load_hooks+ is required, but it has already been required so it's not loaded again.
-
-If +i18n+ cannot be loaded, the user is presented with an error which says that it cannot be loaded and recommends that it's added to the +Gemfile+. However, in a normal Rails application this gem would be loaded.
-
-Once it has finished loading, the +I18n.load_path+ method is used to add the +activesupport/lib/active_support/locale/en.yml+ file to I18n's load path. When the translations are loaded in the initialization process, this is one of the files where they will be sourced from.
-
-The loading of this file finishes the loading of +active_model+ and so we go back to +action_dispatch+.
-
-h4. Back to +activesupport/lib/action_dispatch.rb+
-
-The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here:
-
-<ruby>
-autoload_under 'testing' do
- autoload :Assertions
-...
-</ruby>
-
-The +Assertions+ module is in the +action_dispatch/testing+ folder rather than simply +action_dispatch+.
-
-Finally, this file defines a top-level autoload, the +Mime+ constant.
-
-h4. Back to +activesupport/lib/action_dispatch/railtie.rb+
-
-After +action_dispatch+ is required in this file, the +ActionDispatch::Railtie+ class is defined and is yet another class that inherits from +Rails::Railtie+. This class defines some initial configuration option defaults for +config.action_dispatch+ before setting up a single initializer called +action_dispatch.configure+.
-
-With +action_dispatch/railtie+ now complete, we go back to +railties/lib/rails.rb+.
-
-h4. Back to +railties/lib/rails.rb+
-
-With the Active Support and Action Dispatch railties now both loaded, the rest of this file deals with setting up UTF-8 to be the default encoding for Rails and then finally setting up the +Rails+ module. This module defines useful methods such as +Rails.logger+, +Rails.application+, +Rails.env+, and +Rails.root+.
-
-h4. Back to +railties/lib/rails/all.rb+
-
-Now that +rails.rb+ is required, the remaining railties are loaded next, beginning with +active_record/railtie+.
-
-h4. +activerecord/lib/active_record/railtie.rb+
-
-Before this file gets into the swing of defining the +ActiveRecord::Railtie+ class, there are a couple of files that are required first. The first one of these is +active_record+.
-
-h4. +activerecord/lib/active_record.rb+
-
-This file begins by detecting if the +lib+ directories of +active_support+ and +active_model+ are not in the load path and if they aren't then adds them. As we saw back in +action_dispatch.rb+, these directories are already there.
-
-The first three requires have already been done by other files and so aren't loaded here, but the 4th require, the one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module.
-
-<ruby>
-require 'active_support'
-require 'active_support/i18n'
-require 'active_model'
-require 'arel'
-</ruby>
-
-The 5th require in this file is one to +active_record/version+ which defines the +ActiveRecord::VERSION+ constant:
-
-<ruby>
-module ActiveRecord
- module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 1
- TINY = 0
- PRE = "beta"
-
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
- end
-end
-</ruby>
-
-Once these requires are finished, the base for the +ActiveRecord+ module is defined along with its autoloads.
-
-Near the end of the file, we see this line:
-
-<ruby>
-ActiveSupport.on_load(:active_record) do
- Arel::Table.engine = self
-end
-</ruby>
-
-This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+.
-
-The file then finishes with this line:
-
-<ruby>
-I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
-</ruby>
-
-This will add the translations from +activerecord/lib/active_record/locale/en.yml+ to the load path for +I18n+, with this file being parsed when all the translations are loaded.
-
-h4. Back to +activerecord/lib/active_record/railtie.rb+
-
-The next two <tt>require</tt>s in this file aren't run because their files are already required, with +rails+ being required by +rails/all+ and +active_model/railtie+ being required from +action_dispatch+.
-
-<ruby>
-require "rails"
-require "active_model/railtie"
-</ruby>
-
-The next +require+ in this file is to +action_controller/railtie+.
-
-h4. +actionpack/lib/action_controller/railtie.rb+
-
-This file begins with a couple more requires to files that have already been loaded:
-
-<ruby>
-require "rails"
-require "action_controller"
-require "action_dispatch/railtie"
-</ruby>
-
-However the require after these is to a file that hasn't yet been loaded, +action_view/railtie+, which begins by requiring +action_view+.
-
-h4. +actionpack/lib/action_view.rb+
+We wont dig into the server configuration itself, but this is
+the last piece of our journey in the Rails initialization process.
-+action_view.rb+
+This high level overview will help you understand when you code is
+executed and how, and overall become a better Rails developer. If you
+still want to know more, the Rails source code itself is probably the
+best place to go next.
diff --git a/guides/source/layouts_and_rendering.textile b/guides/source/layouts_and_rendering.textile
index b0a87a5981..55bd521419 100644
--- a/guides/source/layouts_and_rendering.textile
+++ b/guides/source/layouts_and_rendering.textile
@@ -23,8 +23,6 @@ From the controller's point of view, there are three ways to create an HTTP resp
* Call +redirect_to+ to send an HTTP redirect status code to the browser
* Call +head+ to create a response consisting solely of HTTP headers to send back to the browser
-I'll cover each of these methods in turn. But first, a few words about the very easiest thing that the controller can do to create a response: nothing at all.
-
h4. Rendering by Default: Convention Over Configuration in Action
You've heard that Rails promotes "convention over configuration". Default rendering is an excellent example of this. By default, controllers in Rails automatically render views with names that correspond to valid routes. For example, if you have this code in your +BooksController+ class:
diff --git a/guides/source/migrations.textile b/guides/source/migrations.textile
index 52dba76e68..342b5a4d57 100644
--- a/guides/source/migrations.textile
+++ b/guides/source/migrations.textile
@@ -8,8 +8,7 @@ production machines next time you deploy.
Active Record tracks which migrations have already been run so all you have to
do is update your source and run +rake db:migrate+. Active Record will work out
-which migrations should be run. It will also update your +db/schema.rb+ file to
-match the structure of your database.
+which migrations should be run. Active Record will also update your +db/schema.rb+ file to match the up-to-date structure of your database.
Migrations also allow you to describe these transformations using Ruby. The
great thing about this is that (like most of Active Record's functionality) it
diff --git a/guides/source/rails_on_rack.textile b/guides/source/rails_on_rack.textile
index d8910cf1d0..63712b22ef 100644
--- a/guides/source/rails_on_rack.textile
+++ b/guides/source/rails_on_rack.textile
@@ -23,29 +23,49 @@ h3. Rails on Rack
h4. Rails Application's Rack Object
-<tt>ActionController::Dispatcher.new</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ActionController::Dispatcher.new+ object to serve a Rails application.
+<tt>ApplicationName::Application</tt> is the primary Rack application object of a Rails application. Any Rack compliant web server should be using +ApplicationName::Application+ object to serve a Rails application.
h4. +rails server+
-<tt>rails server</tt> does the basic job of creating a +Rack::Builder+ object and starting the webserver. This is Rails' equivalent of Rack's +rackup+ script.
+<tt>rails server</tt> does the basic job of creating a +Rack::Server+ object and starting the webserver.
-Here's how +rails server+ creates an instance of +Rack::Builder+
+Here's how +rails server+ creates an instance of +Rack::Server+
<ruby>
-app = Rack::Builder.new {
- use Rails::Rack::LogTailer unless options[:detach]
- use Rails::Rack::Debugger if options[:debugger]
- use ActionDispatch::Static
- run ActionController::Dispatcher.new
-}.to_app
+Rails::Server.new.tap { |server|
+ require APP_PATH
+ Dir.chdir(Rails.application.root)
+ server.start
+}
</ruby>
-Middlewares used in the code above are primarily useful only in the development environment. The following table explains their usage:
+The +Rails::Server+ inherits from +Rack::Server+ and calls the +Rack::Server#start+ method this way:
+
+<ruby>
+class Server < ::Rack::Server
+ def start
+ ...
+ super
+ end
+end
+</ruby>
+
+Here's how it loads the middlewares:
+
+<ruby>
+def middleware
+ middlewares = []
+ middlewares << [Rails::Rack::Debugger] if options[:debugger]
+ middlewares << [::Rack::ContentLength]
+ Hash.new(middlewares)
+end
+</ruby>
+
++Rails::Rack::Debugger+ is primarily useful only in the development environment. The following table explains the usage of the loaded middlewares:
|_.Middleware|_.Purpose|
-|+Rails::Rack::LogTailer+|Appends log file output to console|
-|+ActionDispatch::Static+|Serves static files inside +Rails.root/public+ directory|
|+Rails::Rack::Debugger+|Starts Debugger|
+|+Rack::ContentLength+|Counts the number of bytes in the response and set the HTTP Content-Length header|
h4. +rackup+
@@ -55,9 +75,9 @@ To use +rackup+ instead of Rails' +rails server+, you can put the following insi
# Rails.root/config.ru
require "config/environment"
-use Rails::Rack::LogTailer
-use ActionDispatch::Static
-run ActionController::Dispatcher.new
+use Rack::Debugger
+use Rack::ContentLength
+run ApplicationName::Application
</ruby>
And start the server:
@@ -72,11 +92,11 @@ To find out more about different +rackup+ options:
$ rackup --help
</shell>
-h3. Action Controller Middleware Stack
+h3. Action Dispatcher Middleware Stack
-Many of Action Controller's internal components are implemented as Rack middlewares. +ActionController::Dispatcher+ uses +ActionController::MiddlewareStack+ to combine various internal and external middlewares to form a complete Rails Rack application.
+Many of Action Dispatchers's internal components are implemented as Rack middlewares. +Rails::Application+ uses +ActionDispatch::MiddlewareStack+ to combine various internal and external middlewares to form a complete Rails Rack application.
-NOTE: +ActionController::MiddlewareStack+ is Rails' equivalent of +Rack::Builder+, but built for better flexibility and more features to meet Rails' requirements.
+NOTE: +ActionDispatch::MiddlewareStack+ is Rails' equivalent of +Rack::Builder+, but built for better flexibility and more features to meet Rails' requirements.
h4. Inspecting Middleware Stack
@@ -111,7 +131,7 @@ use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
-run Blog::Application.routes
+run ApplicationName::Application.routes
</ruby>
Purpose of each of this middlewares is explained in the "Internal Middlewares":#internal-middleware-stack section.
@@ -172,9 +192,26 @@ use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
use Rack::Runtime
...
-run Myapp::Application.routes
+run Blog::Application.routes
</shell>
+If you want to remove session related middleware, do the following:
+
+<ruby>
+# config/application.rb
+config.middleware.delete "ActionDispatch::Cookies"
+config.middleware.delete "ActionDispatch::Session::CookieStore"
+config.middleware.delete "ActionDispatch::Flash"
+</ruby>
+
+And to remove browser related middleware,
+
+<ruby>
+# config/application.rb
+config.middleware.delete "ActionDispatch::BestStandardsSupport"
+config.middleware.delete "Rack::MethodOverride"
+</ruby>
+
h4. Internal Middleware Stack
Much of Action Controller's functionality is implemented as Middlewares. The following list explains the purpose of each of them:
@@ -264,7 +301,7 @@ config.middleware.clear
<ruby>
# config.ru
use MyOwnStackFromScratch
-run ActionController::Dispatcher.new
+run ApplicationName::Application
</ruby>
h3. Resources
diff --git a/guides/source/routing.textile b/guides/source/routing.textile
index 4a50edbb15..dae25853cd 100644
--- a/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -395,7 +395,7 @@ NOTE: You can't use +:namespace+ or +:module+ with a +:controller+ path segment.
get ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
</ruby>
-TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash.
+TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment, add a constraint that overrides this – for example, +:id+ => /[^\/]+/ allows anything except a slash.
h4. Static Segments
@@ -445,6 +445,14 @@ get 'exit' => 'sessions#destroy', :as => :logout
This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/exit+
+You can also use this to override routing methods defined by resources, like this:
+
+<ruby>
+get ':username', :to => "users#show", :as => :user
+</ruby>
+
+This will define a +user_path+ method that will be available in controllers, helpers and views that will go to a route such as +/bob+. Inside the +show+ action of +UsersController+, +params[:username]+ will contain the username for the user. Change +:username+ in the route definition if you do not want your parameter name to be +:username+.
+
h4. HTTP Verb Constraints
In general, you should use the +get+, +post+, +put+ and +delete+ methods to constrain a route to a particular verb. You can use the +match+ method with the +:via+ option to match multiple verbs at once:
@@ -636,6 +644,14 @@ You should put the +root+ route at the top of the file, because it is the most p
NOTE: The +root+ route only routes +GET+ requests to the action.
+h4. Unicode character routes
+
+You can specify unicode character routes directly. For example
+
+<ruby>
+match 'こんにちは' => 'welcome#index'
+</ruby>
+
h3. Customizing Resourceful Routes
While the default routes and helpers generated by +resources :posts+ will usually serve you well, you may want to customize them in some way. Rails allows you to customize virtually any generic part of the resourceful helpers.
@@ -851,9 +867,11 @@ h3. Inspecting and Testing Routes
Rails offers facilities for inspecting and testing your routes.
-h4. Seeing Existing Routes with +rake+
+h4. Seeing Existing Routes
+
+To get a complete list of the available routes in your application, visit +http://localhost:3000/rails/info/routes+ in your browser while your server is running in the *development* environment. You can also execute the +rake routes+ command in your terminal to produce the same output.
-If you want a complete list of all of the available routes in your application, run +rake routes+ command. This will print all of your routes, in the same order that they appear in +routes.rb+. For each route, you'll see:
+Both methods will list all of your routes, in the same order that they appear in +routes.rb+. For each route, you'll see:
* The route name (if any)
* The HTTP verb used (if the route doesn't respond to all verbs)
diff --git a/guides/source/ruby_on_rails_guides_guidelines.textile b/guides/source/ruby_on_rails_guides_guidelines.textile
index f3e934d38c..dd209b61d6 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.textile
+++ b/guides/source/ruby_on_rails_guides_guidelines.textile
@@ -47,7 +47,13 @@ h4. Generation
To generate all the guides, just +cd+ into the *+guides+* directory and execute:
<plain>
-bundle exec rake generate_guides
+bundle exec rake guides:generate
+</plain>
+
+or
+
+<plain>
+bundle exec rake guides:generate:html
</plain>
(You may need to run +bundle install+ first to install the required gems.)
@@ -56,7 +62,7 @@ To process +my_guide.textile+ and nothing else use the +ONLY+ environment variab
<plain>
touch my_guide.textile
-bundle exec rake generate_guides ONLY=my_guide
+bundle exec rake guides:generate ONLY=my_guide
</plain>
By default, guides that have not been modified are not processed, so +ONLY+ is rarely needed in practice.
@@ -68,7 +74,13 @@ It is also recommended that you work with +WARNINGS=1+. This detects duplicate I
If you want to generate guides in a language other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +GUIDES_LANGUAGE+ environment variable:
<plain>
-bundle exec rake generate_guides GUIDES_LANGUAGE=es
+bundle exec rake guides:generate GUIDES_LANGUAGE=es
+</plain>
+
+If you want to see all the environment variables you can use to configure the generation script just run:
+
+<plain>
+rake
</plain>
h4. Validation
@@ -76,7 +88,7 @@ h4. Validation
Please validate the generated HTML with:
<plain>
-bundle exec rake validate_guides
+bundle exec rake guides:validate
</plain>
Particularly, titles get an ID generated from their content and this often leads to duplicates. Please set +WARNINGS=1+ when generating guides to detect them. The warning messages suggest a solution.
@@ -85,8 +97,8 @@ h3. Kindle Guides
h4(#generation-kindle). Generation
-To generate guides for the Kindle, you need to provide +KINDLE=1+ as an environment variable:
+To generate guides for the Kindle, use the following rake task:
<plain>
-KINDLE=1 bundle exec rake generate_guides
+bundle exec rake guides:generate:kindle
</plain>
diff --git a/guides/source/security.textile b/guides/source/security.textile
index ac55d60368..626d6fa508 100644
--- a/guides/source/security.textile
+++ b/guides/source/security.textile
@@ -30,7 +30,7 @@ A good place to start looking at security is with sessions, which can be vulnera
h4. What are Sessions?
--- _HTTP is a stateless protocol. Sessions make it stateful._
+NOTE: _HTTP is a stateless protocol. Sessions make it stateful._
Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.
@@ -44,13 +44,13 @@ User.find(session[:user_id])
h4. Session id
--- _The session id is a 32 byte long MD5 hash value._
+NOTE: _The session id is a 32 byte long MD5 hash value._
A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date.
h4. Session Hijacking
--- _Stealing a user's session id lets an attacker use the web application in the victim's name._
+WARNING: _Stealing a user's session id lets an attacker use the web application in the victim's name._
Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session.
@@ -72,7 +72,7 @@ The main objective of most attackers is to make money. The underground prices fo
h4. Session Guidelines
--- _Here are some general guidelines on sessions._
+Here are some general guidelines on sessions.
* _(highlight)Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below).
This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate.
@@ -81,7 +81,7 @@ This will also be a good idea, if you modify the structure of an object and old
h4. Session Storage
--- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecord::SessionStore and ActionDispatch::Session::CookieStore._
+NOTE: _Rails provides several storage mechanisms for the session hashes. The most important are +ActiveRecord::SessionStore+ and +ActionDispatch::Session::CookieStore+._
There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose ActiveRecord::SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. ActiveRecord::SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request.
@@ -104,7 +104,7 @@ There are, however, derivatives of CookieStore which encrypt the session hash, s
h4. Replay Attacks for CookieStore Sessions
--- _Another sort of attack you have to be aware of when using CookieStore is the replay attack._
+TIP: _Another sort of attack you have to be aware of when using +CookieStore+ is the replay attack._
It works like this:
@@ -120,7 +120,7 @@ The best _(highlight)solution against it is not to store this kind of data in a
h4. Session Fixation
--- _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._
+NOTE: _Apart from stealing a user's session id, the attacker may fix a session id known to him. This is called session fixation._
!images/session_fixation.png(Session fixation)!
@@ -135,7 +135,7 @@ This attack focuses on fixing a user's session id known to the attacker, and for
h4. Session Fixation – Countermeasures
--- _One line of code will protect you from session fixation._
+TIP: _One line of code will protect you from session fixation._
The most effective countermeasure is to _(highlight)issue a new session identifier_ and declare the old one invalid after a successful login. That way, an attacker cannot use the fixed session identifier. This is a good countermeasure against session hijacking, as well. Here is how to create a new session in Rails:
@@ -149,7 +149,7 @@ Another countermeasure is to _(highlight)save user-specific properties in the se
h4. Session Expiry
--- _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._
+NOTE: _Sessions that never expire extend the time-frame for attacks such as cross-site reference forgery (CSRF), session hijacking and session fixation._
One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to _(highlight)expire sessions in a database table_. Call +Session.sweep("20 minutes")+ to expire sessions that were used longer than 20 minutes ago.
@@ -174,7 +174,7 @@ delete_all "updated_at < '#{time.ago.to_s(:db)}' OR
h3. Cross-Site Request Forgery (CSRF)
--- _This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands._
+This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands.
!images/csrf.png!
@@ -193,7 +193,7 @@ CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) -- less t
h4. CSRF Countermeasures
--- _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._
+NOTE: _First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF._
The HTTP protocol basically provides two main types of requests - GET and POST (and more, but they are not supported by most browsers). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST:
@@ -236,6 +236,17 @@ protect_from_forgery :secret => "123456789012345678901234567890..."
This will automatically include a security token, calculated from the current session and the server-side secret, in all forms and Ajax requests generated by Rails. You won't need the secret, if you use CookieStorage as session storage. If the security token doesn't match what was expected, the session will be reset. *Note:* In Rails versions prior to 3.0.4, this raised an <tt>ActionController::InvalidAuthenticityToken</tt> error.
+It is common to use persistent cookies to store user information, with +cookies.permanent+ for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:
+
+<ruby>
+def handle_unverified_request
+ super
+ sign_out_user # Example method that will destroy the user cookies.
+end
+</ruby>
+
+The above method can be placed in the +ApplicationController+ and will be called when a CSRF token is not present on a non-GET request.
+
Note that _(highlight)cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so he can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.
h3. Redirection and Files
@@ -244,7 +255,7 @@ Another class of security vulnerabilities surrounds the use of redirection and f
h4. Redirection
--- _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._
+WARNING: _Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, he may also create a self-contained attack._
Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: http://www.example.com/site/redirect?to= www.attacker.com. Here is an example of a legacy action:
@@ -272,7 +283,7 @@ This example is a Base64 encoded JavaScript which displays a simple message box.
h4. File Uploads
--- _Make sure file uploads don't overwrite important files, and process media files asynchronously._
+NOTE: _Make sure file uploads don't overwrite important files, and process media files asynchronously._
Many web applications allow users to upload files. _(highlight)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.
@@ -297,7 +308,7 @@ The solution to this is best to _(highlight)process media files asynchronously_:
h4. Executable Code in File Uploads
--- _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._
+WARNING: _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._
The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file “file.cgi” with code in it, which will be executed when someone downloads the file.
@@ -305,7 +316,7 @@ _(highlight)If your Apache DocumentRoot points to Rails' /public directory, do n
h4. File Downloads
--- _Make sure users cannot download arbitrary files._
+NOTE: _Make sure users cannot download arbitrary files._
Just as you have to filter file names for uploads, you have to do so for downloads. The send_file() method sends files from the server to the client. If you use a file name, that the user entered, without filtering, any file can be downloaded:
@@ -327,7 +338,7 @@ Another (additional) approach is to store the file names in the database and nam
h3. Intranet and Admin Security
--- _Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world._
+Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world.
In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.

@@ -359,7 +370,7 @@ The common admin interface works like this: it's located at www.example.com/admi
h3. Mass Assignment
--- _Without any precautions Model.new(params[:model]) allows attackers to set any database column's value._
+WARNING: _Without any precautions +Model.new(params[:model]+) allows attackers to set any database column's value._
The mass-assignment feature may become a problem, as it allows an attacker to set any model's attributes by manipulating the hash passed to a model's +new()+ method:
@@ -471,7 +482,7 @@ This will create an empty whitelist of attributes available for mass-assignment
h3. User Management
--- _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._
+NOTE: _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._
There are a number of authentication plug-ins for Rails available. Good ones, such as the popular "devise":https://github.com/plataformatec/devise and "authlogic":https://github.com/binarylogic/authlogic, store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in +has_secure_password+ method which has similar features.
@@ -498,7 +509,7 @@ And thus it found the first user in the database, returned it and logged him in.
h4. Brute-Forcing Accounts
--- _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._
+NOTE: _Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA._
A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes.
@@ -510,7 +521,7 @@ In order to mitigate such attacks, _(highlight)display a generic error message o
h4. Account Hijacking
--- _Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?_
+Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?.
h5. Passwords
@@ -526,7 +537,7 @@ Depending on your web application, there may be more ways to hijack the user's a
h4. CAPTCHAs
--- _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
+INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that he is human, but reveal that a robot is a robot._
But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is "reCAPTCHA":http://recaptcha.net/ which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. "ReCAPTCHA":http://ambethia.com/recaptcha/ is also a Rails plug-in with the same name as the API.
@@ -553,7 +564,7 @@ Note that this protects you only from automatic bots, targeted tailor-made bots
h4. Logging
--- _Tell Rails not to put passwords in the log files._
+WARNING: _Tell Rails not to put passwords in the log files._
By default, Rails logs all requests being made to the web application. But log files can be a huge security issue, as they may contain login credentials, credit card numbers et cetera. When designing a web application security concept, you should also think about what will happen if an attacker got (full) access to the web server. Encrypting secrets and passwords in the database will be quite useless, if the log files list them in clear text. You can _(highlight)filter certain request parameters from your log files_ by appending them to <tt>config.filter_parameters</tt> in the application configuration. These parameters will be marked [FILTERED] in the log.
@@ -563,7 +574,7 @@ config.filter_parameters << :password
h4. Good Passwords
--- _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
+INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
Bruce Schneier, a security technologist, "has analyzed":http://www.schneier.com/blog/archives/2006/12/realworld_passw.html 34,000 real-world user names and passwords from the MySpace phishing attack mentioned <a href="#examples-from-the-underground">below</a>. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are:
@@ -575,31 +586,48 @@ A good password is a long alphanumeric combination of mixed cases. As this is qu
h4. Regular Expressions
--- _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._
+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? Imagine you have a File model and you validate the file name by a 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 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>
-class File < ActiveRecord::Base
- validates :name, :format => /^[\w\.\-\<plus>]<plus>$/
-end
+ /^https?:\/\/[^\n]+$/i
</ruby>
-This means, upon saving, the model will validate the file name to consist only of alphanumeric characters, dots, + and -. And the programmer added ^ and $ so that file name will contain these characters from the beginning to the end of the string. However, _(highlight)in Ruby ^ and $ matches the *line* beginning and line end_. And thus a file name like this passes the filter without problems:
+This may work fine in some languages. However, _(highlight)in Ruby ^ and $ match the *line* beginning and line end_. And thus a URL like this passes the filter without problems:
<plain>
-file.txt%0A<script>alert('hello')</script>
+javascript:exploit_code();/*
+http://hi.com
+*/
</plain>
-Whereas %0A is a line feed in URL encoding, so Rails automatically converts it to "file.txt\n&lt;script&gt;alert('hello')&lt;/script&gt;". This file name passes the filter because the regular expression matches – up to the line end, the rest does not matter. The correct expression should read:
+This URL passes the filter because the regular expression matches – the second line, the rest does not matter. Now imagine we had a view that showed the URL like this:
<ruby>
-/\A[\w\.\-\<plus>]<plus>\z/
+ link_to "Homepage", @user.homepage
</ruby>
+The link looks innocent to visitors, but when it's clicked, it will execute the javascript function "exploit_code" or any other javascript the attacker provides.
+
+To fix the regular expression, \A and \z should be used instead of ^ and $, like so:
+
+<ruby>
+ /\Ahttps?:\/\/[^\n]+\z/i
+</ruby>
+
+Since this is a frequent mistake, the format validator (validates_format_of) now raises an exception if the provided regular expression starts with ^ or ends with $. If you do need to use ^ and $ instead of \A and \z (which is rare), you can set the :multiline option to true, like so:
+
+<ruby>
+ # content should include a line "Meanwhile" anywhere in the string
+ validates :content, :format => { :with => /^Meanwhile$/, :multiline => true }
+</ruby>
+
+Note that this only protects you against the most common mistake when using the format validator - you always need to keep in mind that ^ and $ match the *line* beginning and line end in Ruby, and not the beginning and end of a string.
+
h4. Privilege Escalation
--- _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._
+WARNING: _Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it._
The most common parameter that a user might tamper with, is the id parameter, as in +http://www.domain.com/project/1+, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this:
@@ -619,13 +647,13 @@ Don't be fooled by security by obfuscation and JavaScript security. The Web Deve
h3. Injection
--- _Injection is a class of attacks that introduce malicious code or parameters into a web application in order to run it within its security context. Prominent examples of injection are cross-site scripting (XSS) and SQL injection._
+INFO: _Injection is a class of attacks that introduce malicious code or parameters into a web application in order to run it within its security context. Prominent examples of injection are cross-site scripting (XSS) and SQL injection._
Injection is very tricky, because the same code or parameter can be malicious in one context, but totally harmless in another. A context can be a scripting, query or programming language, the shell or a Ruby/Rails method. The following sections will cover all important contexts where injection attacks may happen. The first section, however, covers an architectural decision in connection with Injection.
h4. Whitelists versus Blacklists
--- _When sanitizing, protecting or verifying something, whitelists over blacklists._
+NOTE: _When sanitizing, protecting or verifying something, whitelists over blacklists._
A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _(highlight)prefer to use whitelist approaches_:
@@ -640,7 +668,7 @@ Whitelists are also a good approach against the human factor of forgetting somet
h4. SQL Injection
--- _Thanks to clever methods, this is hardly a problem in most Rails applications. However, this is a very devastating and common attack in web applications, so it is important to understand the problem._
+INFO: _Thanks to clever methods, this is hardly a problem in most Rails applications. However, this is a very devastating and common attack in web applications, so it is important to understand the problem._
h5(#sql-injection-introduction). Introduction
@@ -719,7 +747,7 @@ The array or hash form is only available in model instances. You can try +saniti
h4. Cross-Site Scripting (XSS)
--- _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._
+INFO: _The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off._
h5. Entry Points
@@ -847,7 +875,7 @@ The MySpace Samy worm will be discussed in the CSS Injection section.
h4. CSS Injection
--- _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._
+INFO: _CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application._
CSS Injection is explained best by a well-known worm, the "MySpace Samy worm":http://namb.la/popular/tech.html. This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, but it creates too much traffic on MySpace, so that the site goes offline. The following is a technical explanation of the worm.
@@ -887,7 +915,7 @@ This example, again, showed that a blacklist filter is never complete. However,
h4. Textile Injection
--- _If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS._
+If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. "RedCloth":http://redcloth.org/ is such a language for Ruby, but without precautions, it is also vulnerable to XSS.
For example, RedCloth translates +_test_+ to &lt;em&gt;test&lt;em&gt;, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4:
@@ -916,13 +944,13 @@ It is recommended to _(highlight)use RedCloth in combination with a whitelist in
h4. Ajax Injection
--- _The same security precautions have to be taken for Ajax actions as for “normal” ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._
+NOTE: _The same security precautions have to be taken for Ajax actions as for “normal” ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._
If you use the "in_place_editor plugin":http://dev.rubyonrails.org/browser/plugins/in_place_editing, or actions that return a string, rather than rendering a view, _(highlight)you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method.
h4. Command Line Injection
--- _Use user-supplied command line parameters with caution._
+NOTE: _Use user-supplied command line parameters with caution._
If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and `command`. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|).
@@ -936,7 +964,7 @@ system("/bin/echo","hello; rm *")
h4. Header Injection
--- _HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting._
+WARNING: _HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting._
HTTP request headers have a Referer, User-Agent (client software), and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. _(highlight)Remember to escape these header fields, too._ For example when you display the user agent in an administration area.
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
index 02407a5fe8..4bf4751127 100644
--- a/guides/source/upgrading_ruby_on_rails.textile
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -34,17 +34,27 @@ h4(#plugins4_0). vendor/plugins
Rails 4.0 no longer supports loading plugins from <tt>vendor/plugins</tt>. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
-h4(#identity_map4_0). IdentityMap
+h4(#identity_map4_0). Identity Map
-Rails 4.0 has removed <tt>IdentityMap</tt> from <tt>ActiveRecord</tt>, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
+Rails 4.0 has removed the identity map from Active Record, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
-h4(#active_model4_0). ActiveModel
+h4(#active_record4_0). Active Record
-Rails 4.0 has changed how errors attach with the ConfirmationValidator. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+The <tt>delete</tt> method in collection associations can now receive <tt>Fixnum</tt> or <tt>String</tt> arguments as record ids, besides records, pretty much like the <tt>destroy</tt> method does. Previously it raised <tt>ActiveRecord::AssociationTypeMismatch</tt> for such arguments. From Rails 4.0 on <tt>delete</tt> automatically tries to find the records matching the given ids before deleting them.
-h4(#action_pack4_0). ActionPack
+h4(#active_model4_0). Active Model
-Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>RoutingError</tt>.
+Rails 4.0 has changed how errors attach with the <tt>ActiveModel::Validations::ConfirmationValidator</tt>. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+
+h4(#action_pack4_0). Action Pack
+
+Rails 4.0 changed how <tt>assert_generates</tt>, <tt>assert_recognizes</tt>, and <tt>assert_routing</tt> work. Now all these assertions raise <tt>Assertion</tt> instead of <tt>ActionController::RoutingError</tt>.
+
+Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, e.g. <tt>get Rack::Utils.escape('こんにちは'), :controller => 'welcome', :action => 'index'</tt> to <tt>get 'こんにちは', :controller => 'welcome', :action => 'index'</tt>.
+
+h4(#helpers_order). Helpers Loading Order
+
+The loading order of helpers from more than one directory has changed in Rails 4.0. Previously, helpers from all directories were gathered and then sorted alphabetically. After upgrade to Rails 4.0 helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use <tt>helpers_path</tt> parameter, this change will only impact the way of loading helpers from engines. If you rely on the fact that particular helper from engine loads before or after another helper from application or another engine, you should check if correct methods are available after upgrade. If you would like to change order in which engines are loaded, you can use <tt>config.railties_order=</tt> method.
h3. Upgrading from Rails 3.1 to Rails 3.2
diff --git a/rails.gemspec b/rails.gemspec
index 8314036ad1..52f92f46c6 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -9,6 +9,7 @@ Gem::Specification.new do |s|
s.required_ruby_version = '>= 1.9.3'
s.required_rubygems_version = ">= 1.8.11"
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index ccccc178c5..e19aa68407 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* Set `config.active_record.migration_error` to `:page_load` for development *Richard Schneeman*
+
+* Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj*
+
+* Add `/rails/info/routes` path, displays same information as `rake routes` *Richard Schneeman & Andrew White*
+
* Improved `rake routes` output for redirects *Łukasz Strzałkowski & Andrew White*
* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index c4edbae55b..32797ee657 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -75,8 +75,12 @@ module Rails
def initialize
super
- @initialized = false
- @reloaders = []
+ @initialized = false
+ @reloaders = []
+ @routes_reloader = nil
+ @env_config = nil
+ @ordered_railties = nil
+ @queue = nil
end
# This method is called just after an application inherits from Rails::Application,
@@ -93,7 +97,7 @@ module Rails
# Rails application, you will need to add lib to $LOAD_PATH on your own in case
# you need to load files in lib/ during the application configuration as well.
def add_lib_to_load_path! #:nodoc:
- path = config.root.join('lib').to_s
+ path = File.join config.root, 'lib'
$LOAD_PATH.unshift(path) if File.exists?(path)
end
@@ -154,6 +158,14 @@ module Rails
self
end
+ # Load the application runner and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.runner</tt> for more info.
+ def load_runner(app=self)
+ initialize_runner
+ super
+ self
+ end
+
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
def env_config
@@ -181,7 +193,7 @@ module Rails
end
all = (railties.all - order)
- all.push(self) unless all.include?(self)
+ all.push(self) unless (all + order).include?(self)
order.push(:all) unless order.include?(:all)
index = order.index(:all)
@@ -300,6 +312,9 @@ module Rails
require "rails/console/helpers"
end
+ def initialize_runner #:nodoc:
+ end
+
def build_original_fullpath(env)
path_info = env["PATH_INFO"]
query_string = env["QUERY_STRING"]
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 84f2601f28..60aa40b92f 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -23,6 +23,8 @@ module Rails
if Rails.env.development?
app.routes.append do
get '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/routes' => "rails/info#routes"
+ get '/rails/info' => "rails/info#index"
end
end
end
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb
index b23fb3e920..942c4f4789 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/route_inspector.rb
@@ -51,7 +51,7 @@ module Rails
end
def internal?
- path =~ %r{/rails/info/properties|^#{Rails.application.config.assets.prefix}}
+ path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}}
end
def engine?
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index bcac0751d6..1aed2796c1 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -26,7 +26,7 @@ class CodeStatistics #:nodoc:
Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}]
end
- def calculate_directory_statistics(directory, pattern = /.*\.rb$/)
+ def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee)$/)
stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
Dir.foreach(directory) do |file_name|
@@ -39,6 +39,13 @@ class CodeStatistics #:nodoc:
comment_started = false
+ case file_name
+ when /.*\.js$/
+ comment_pattern = /^\s*\/\//
+ else
+ comment_pattern = /^\s*#/
+ end
+
File.open(directory + "/" + file_name) do |f|
while line = f.gets
stats["lines"] += 1
@@ -55,7 +62,7 @@ class CodeStatistics #:nodoc:
end
stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/
stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/
- stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
+ stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ comment_pattern
end
end
end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 7f473c237c..8816387d34 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -36,9 +36,17 @@ when 'benchmarker', 'profiler'
when 'console'
require 'rails/commands/console'
+ options = Rails::Console.parse_arguments(ARGV)
+
+ # RAILS_ENV needs to be set before config/application is required
+ ENV['RAILS_ENV'] = options[:environment] if options[:environment]
+
+ # shift ARGV so IRB doesn't freak
+ ARGV.shift if ARGV.first && ARGV.first[0] != '-'
+
require APP_PATH
Rails.application.require_environment!
- Rails::Console.start(Rails.application)
+ Rails::Console.start(Rails.application, options)
when 'server'
# Change to the application's path if there is no config.ru file in current dir.
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index cd6a03fe51..92cee6b638 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -4,25 +4,16 @@ require 'irb/completion'
module Rails
class Console
- attr_reader :options, :app, :console, :arguments
-
- def self.start(*args)
- new(*args).start
- end
-
- def initialize(app, arguments = ARGV)
- @app = app
- @arguments = arguments
- app.load_console
- @console = app.config.console || IRB
- end
+ class << self
+ def start(*args)
+ new(*args).start
+ end
- def options
- @options ||= begin
+ def parse_arguments(arguments)
options = {}
OptionParser.new do |opt|
- opt.banner = "Usage: console [environment] [options]"
+ opt.banner = "Usage: rails console [environment] [options]"
opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v }
opt.on("-e", "--environment=name", String,
"Specifies the environment to run this console under (test/development/production).",
@@ -31,20 +22,38 @@ module Rails
opt.parse!(arguments)
end
+ if arguments.first && arguments.first[0] != '-'
+ env = arguments.first
+ options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ end
+
options
end
end
+ attr_reader :options, :app, :console
+
+ def initialize(app, options={})
+ @app = app
+ @options = options
+ app.load_console
+ @console = app.config.console || IRB
+ end
+
def sandbox?
options[:sandbox]
end
+ def environment
+ options[:environment] ||= ENV['RAILS_ENV'] || 'development'
+ end
+
def environment?
- options[:environment]
+ environment
end
def set_environment!
- Rails.env = options[:environment]
+ Rails.env = environment
end
def debugger?
@@ -53,9 +62,7 @@ module Rails
def start
app.sandbox = sandbox?
-
require_debugger if debugger?
-
set_environment! if environment?
if sandbox?
@@ -82,8 +89,3 @@ module Rails
end
end
end
-
-# Has to set the RAILS_ENV before config/application is required
-if ARGV.first && !ARGV.first.index("-") && env = ARGV.shift # has to shift the env ARGV so IRB doesn't freak
- ENV['RAILS_ENV'] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
-end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index aaba47117f..cc0552184a 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -5,61 +5,19 @@ require 'rbconfig'
module Rails
class DBConsole
- attr_reader :arguments, :config
-
+ attr_reader :config, :arguments
+
def self.start
- new(config).start
- end
-
- def self.config
- config = begin
- YAML.load(ERB.new(IO.read("config/database.yml")).result)
- rescue SyntaxError, StandardError
- require APP_PATH
- Rails.application.config.database_configuration
- end
-
- unless config[env]
- abort "No database is configured for the environment '#{env}'"
- end
-
- config[env]
- end
-
- def self.env
- if Rails.respond_to?(:env)
- Rails.env
- else
- ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
- end
+ new.start
end
- def initialize(config, arguments = ARGV)
- @config, @arguments = config, arguments
+ def initialize(arguments = ARGV)
+ @arguments = arguments
end
def start
- include_password = false
- options = {}
- OptionParser.new do |opt|
- opt.banner = "Usage: dbconsole [environment] [options]"
- opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
- include_password = true
- end
-
- opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'],
- "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode|
- options['mode'] = mode
- end
-
- opt.on("--header") do |h|
- options['header'] = h
- end
-
- opt.parse!(arguments)
- abort opt.to_s unless (0..1).include?(arguments.size)
- end
-
+ options = parse_arguments(arguments)
+ ENV['RAILS_ENV'] = options[:environment] || environment
case config["adapter"]
when /^mysql/
@@ -71,7 +29,7 @@ module Rails
'encoding' => '--default-character-set'
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
- if config['password'] && include_password
+ if config['password'] && options['include_password']
args << "--password=#{config['password']}"
elsif config['password'] && !config['password'].to_s.empty?
args << "-p"
@@ -85,7 +43,7 @@ module Rails
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
- ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && include_password
+ ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && options['include_password']
find_cmd_and_exec('psql', config["database"])
when "sqlite"
@@ -96,7 +54,7 @@ module Rails
args << "-#{options['mode']}" if options['mode']
args << "-header" if options['header']
- args << config['database']
+ args << File.expand_path(config['database'], Rails.root)
find_cmd_and_exec('sqlite3', *args)
@@ -105,7 +63,7 @@ module Rails
if config['username']
logon = config['username']
- logon << "/#{config['password']}" if config['password'] && include_password
+ logon << "/#{config['password']}" if config['password'] && options['include_password']
logon << "@#{config['database']}" if config['database']
end
@@ -116,8 +74,73 @@ module Rails
end
end
+ def config
+ @config ||= begin
+ cfg = begin
+ cfg = YAML.load(ERB.new(IO.read("config/database.yml")).result)
+ rescue SyntaxError, StandardError
+ require APP_PATH
+ Rails.application.config.database_configuration
+ end
+
+ unless cfg[environment]
+ abort "No database is configured for the environment '#{environment}'"
+ end
+
+ cfg[environment]
+ end
+ end
+
+ def environment
+ if Rails.respond_to?(:env)
+ Rails.env
+ else
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+ end
+ end
+
protected
+ def parse_arguments(arguments)
+ options = {}
+
+ OptionParser.new do |opt|
+ opt.banner = "Usage: rails dbconsole [environment] [options]"
+ opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
+ options['include_password'] = true
+ end
+
+ opt.on("--mode [MODE]", ['html', 'list', 'line', 'column'],
+ "Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode|
+ options['mode'] = mode
+ end
+
+ opt.on("--header") do |h|
+ options['header'] = h
+ end
+
+ opt.on("-h", "--help", "Show this help message.") do
+ puts opt
+ exit
+ end
+
+ opt.on("-e", "--environment=name", String,
+ "Specifies the environment to run this console under (test/development/production).",
+ "Default: development"
+ ) { |v| options[:environment] = v.strip }
+
+ opt.parse!(arguments)
+ abort opt.to_s unless (0..1).include?(arguments.size)
+ end
+
+ if arguments.first && arguments.first[0] != '-'
+ env = arguments.first
+ options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ end
+
+ options
+ end
+
def find_cmd_and_exec(commands, *args)
commands = Array(commands)
@@ -140,8 +163,3 @@ module Rails
end
end
end
-
-# Has to set the RAILS_ENV before config/application is required
-if ARGV.first && !ARGV.first.index("-") && env = ARGV.first
- ENV['RAILS_ENV'] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
-end
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 2802981e7a..a672258aa6 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -9,7 +9,7 @@ if ARGV.first.nil?
end
ARGV.clone.options do |opts|
- opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)"
+ opts.banner = "Usage: rails runner [options] ('Some.ruby(code)' or a filename)"
opts.separator ""
@@ -41,6 +41,7 @@ ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
+ Rails.application.load_runner
if code_or_file.nil?
$stderr.puts "Run '#{$0} -h' for help."
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 4c4caad69f..9ef64da3ef 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -32,11 +32,6 @@ module Rails
opt_parser.parse! args
- # Handle's environment like RAILS_ENV=production passed in directly
- if index = args.index {|arg| arg.include?("RAILS_ENV")}
- options[:environment] ||= args.delete_at(index).split('=').last
- end
-
options[:server] = args.shift
options
end
@@ -69,7 +64,7 @@ module Rails
#Create required tmp directories if not found
%w(cache pids sessions sockets).each do |dir_to_make|
- FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
+ FileUtils.mkdir_p(File.join(Rails.root, 'tmp', dir_to_make))
end
unless options[:daemonize]
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 3d66019e5e..5fa7f043c6 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -39,25 +39,25 @@ module Rails
end
def insert_before(*args, &block)
- @operations << [:insert_before, args, block]
+ @operations << [__method__, args, block]
end
alias :insert :insert_before
def insert_after(*args, &block)
- @operations << [:insert_after, args, block]
+ @operations << [__method__, args, block]
end
def swap(*args, &block)
- @operations << [:swap, args, block]
+ @operations << [__method__, args, block]
end
def use(*args, &block)
- @operations << [:use, args, block]
+ @operations << [__method__, args, block]
end
def delete(*args, &block)
- @operations << [:delete, args, block]
+ @operations << [__method__, args, block]
end
def merge_into(other) #:nodoc:
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 47856c87c6..806b553b81 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -340,6 +340,18 @@ module Rails
autoload :Configuration, "rails/engine/configuration"
autoload :Railties, "rails/engine/railties"
+ def initialize
+ @_all_autoload_paths = nil
+ @_all_load_paths = nil
+ @app = nil
+ @config = nil
+ @env_config = nil
+ @helpers = nil
+ @railties = nil
+ @routes = nil
+ super
+ end
+
def load_generators(app=self)
initialize_generators
railties.all { |r| r.load_generators(app) }
@@ -404,9 +416,9 @@ module Rails
# Finds engine with given path
def find(path)
- expanded_path = File.expand_path path.to_s
+ expanded_path = File.expand_path path
Rails::Engine::Railties.engines.find { |engine|
- File.expand_path(engine.root.to_s) == expanded_path
+ File.expand_path(engine.root) == expanded_path
}
end
end
@@ -425,6 +437,11 @@ module Rails
super
end
+ def load_runner(app=self)
+ railties.all { |r| r.load_runner(app) }
+ super
+ end
+
def eager_load!
railties.all(&:eager_load!)
@@ -615,14 +632,14 @@ module Rails
end
end
- protected
+ protected
def initialize_generators
require "rails/generators"
end
def routes?
- defined?(@routes) && @routes
+ @routes
end
def has_migrations?
@@ -640,8 +657,7 @@ module Rails
root = File.exist?("#{root_path}/#{flag}") ? root_path : default
raise "Could not find root path for #{self}" unless root
- RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ?
- Pathname.new(root).expand_path : Pathname.new(root).realpath
+ Pathname.new File.realpath root
end
def default_middleware_stack
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 6cd2ea2bbd..c41acc7841 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -5,8 +5,7 @@ module Rails
module Generators
module Actions
- # Adds an entry into Gemfile for the supplied gem. If env
- # is specified, add the gem to the given environment.
+ # Adds an entry into Gemfile for the supplied gem.
#
# gem "rspec", :group => :test
# gem "technoweenie-restful-authentication", :lib => "restful-authentication", :source => "http://gems.github.com/"
@@ -27,7 +26,7 @@ module Rails
log :gemfile, message
options.each do |option, value|
- parts << ":#{option} => #{value.inspect}"
+ parts << "#{option}: #{value.inspect}"
end
in_root do
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 2c1742c6be..5838c9fc38 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -230,7 +230,7 @@ module Rails
if defined?(JRUBY_VERSION)
"gem 'therubyrhino'\n"
else
- "# gem 'therubyracer', platform: :ruby\n"
+ "# gem 'therubyracer', platforms: :ruby\n"
end
end
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 28d7680669..a49a1724c5 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -20,6 +20,7 @@ module Rails
include Rails::Generators::Actions
add_runtime_options!
+ strict_args_position!
# Returns the source root for this generator using default_source_root as default.
def self.source_root(path=nil)
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 63703176de..b61a5fc69d 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -84,10 +84,11 @@ module Rails
end
def namespaced_class_path
- @namespaced_class_path ||= begin
- namespace_path = namespace.name.split("::").map {|m| m.underscore }
- namespace_path + @class_path
- end
+ @namespaced_class_path ||= [namespaced_path] + @class_path
+ end
+
+ def namespaced_path
+ @namespaced_path ||= namespace.name.split("::").map {|m| m.underscore }[0]
end
def class_name
@@ -134,7 +135,7 @@ module Rails
end
def route_url
- @route_url ||= class_path.collect{|dname| "/" + dname }.join('') + "/" + plural_file_name
+ @route_url ||= class_path.collect {|dname| "/" + dname }.join + "/" + plural_file_name
end
# Tries to retrieve the application name or simple return application.
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index bf47e66cc4..55a6b3f4f2 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -19,7 +19,7 @@ source 'https://rubygems.org'
# gem 'unicorn'
# Deploy with Capistrano
-# gem 'capistrano', :group => :development
+# gem 'capistrano', group: :development
# To use debugger
# gem 'debugger'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
index fe9466b366..e1a00d076f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
@@ -22,8 +22,8 @@ development:
# Minimum log levels, in increasing order:
# debug5, debug4, debug3, debug2, debug1,
# log, notice, warning, error, fatal, and panic
- # The server defaults to notice.
- #min_messages: warning
+ # Defaults to warning.
+ #min_messages: notice
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
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 467a4e725f..07223a71c9 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
@@ -35,8 +35,8 @@ development:
# Minimum log levels, in increasing order:
# debug5, debug4, debug3, debug2, debug1,
# log, notice, warning, error, fatal, and panic
- # The server defaults to notice.
- #min_messages: warning
+ # Defaults to warning.
+ #min_messages: notice
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 24bcec854c..01f9396403 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -26,6 +26,9 @@
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL).
config.active_record.auto_explain_threshold_in_seconds = 0.5
+
+ # Raise an error on page load if there are pending migrations
+ config.active_record.migration_error = :page_load
<%- end -%>
<%- unless options.skip_sprockets? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index 19cbf0e4f1..280f777cc0 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -9,8 +9,8 @@ ActiveSupport.on_load(:action_controller) do
end
<%- unless options.skip_active_record? -%>
-# Disable root element in JSON by default.
-ActiveSupport.on_load(:active_record) do
- self.include_root_in_json = false
-end
+# To enable root element in JSON for ActiveRecord objects.
+# ActiveSupport.on_load(:active_record) do
+# self.include_root_in_json = true
+# end
<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index 286e93c3cf..303e47877f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,6 +1,10 @@
<%= app_const %>.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
+
+ # You can have the root of your site routed with "root"
+ # just remember to delete public/index.html.
+ # root :to => 'welcome#index'
# Sample of regular route:
# get 'products/:id' => 'catalog#view'
@@ -46,9 +50,6 @@
# resources :products
# end
- # You can have the root of your site routed with "root"
- # just remember to delete public/index.html.
- # root :to => 'welcome#index'
# See how all your routes lay out with "rake routes"
end \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html
index 276c8c1c6a..3d875c342e 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -22,5 +22,6 @@
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
+ <p>If you are the application owner check the logs for more information.</p>
</body>
</html>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html
index dfdb7d0b05..012977d3d2 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -21,5 +21,6 @@
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
</div>
+ <p>If you are the application owner check the logs for more information.</p>
</body>
</html>
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
index a8f7aeac7d..0090293200 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
@@ -4,6 +4,8 @@ require 'rails/test_help'
class ActiveSupport::TestCase
<% unless options[:skip_active_record] -%>
+ ActiveRecord::Migration.check_pending!
+
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb
index 9b04192126..633e0b3177 100644
--- a/railties/lib/rails/generators/rails/controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb
@@ -1,7 +1,7 @@
<% if namespaced? -%>
-require "<%= namespaced_file_path %>/application_controller"
-<% end -%>
+require_dependency "<%= namespaced_path %>/application_controller"
+<% end -%>
<% module_namespacing do -%>
class <%= class_name %>Controller < ApplicationController
<% actions.each do |action| -%>
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 722e37e20b..ab0e440bc4 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -139,7 +139,7 @@ task :default => :test
gemfile_in_app_path = File.join(rails_app_path, "Gemfile")
if File.exist? gemfile_in_app_path
- entry = "gem '#{name}', :path => '#{relative_path}'"
+ entry = "gem '#{name}', path: '#{relative_path}'"
append_file gemfile_in_app_path, entry
end
end
@@ -232,6 +232,18 @@ task :default => :test
public_task :apply_rails_template, :run_bundle
+ def name
+ @name ||= begin
+ # same as ActiveSupport::Inflector#underscore except not replacing '-'
+ underscored = original_name.dup
+ underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
+ underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
+ underscored.downcase!
+
+ underscored
+ end
+ end
+
protected
def app_templates_dir
@@ -268,18 +280,6 @@ task :default => :test
@original_name ||= File.basename(destination_root)
end
- def name
- @name ||= begin
- # same as ActiveSupport::Inflector#underscore except not replacing '-'
- underscored = original_name.dup
- underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
- underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
- underscored.downcase!
-
- underscored
- end
- end
-
def camelized
@camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index 82ffeebb86..568ed653b7 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -22,6 +22,8 @@ Gem::Specification.new do |s|
<% if full? && !options[:skip_javascript] -%>
# s.add_dependency "<%= "#{options[:javascript]}-rails" %>"
<% end -%>
+<% unless options[:skip_active_record] -%>
s.add_development_dependency "<%= gem_for_database %>"
+<% end -%>
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index 9399c9cb77..7448b386c5 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -1,17 +1,32 @@
source "http://rubygems.org"
+<% if options[:skip_gemspec] -%>
+<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>"
+<% if full? && !options[:skip_javascript] -%>
+# gem "<%= "#{options[:javascript]}-rails" %>"
+<% end -%>
+<% else -%>
# Declare your gem's dependencies in <%= name %>.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec
+<% end -%>
+<% unless options[:javascript] == 'jquery' -%>
# jquery-rails is used by the dummy application
gem "jquery-rails"
+<% end -%>
+<% if options[:skip_gemspec] -%>
+group :development do
+ gem "<%= gem_for_database %>"
+end
+<% else -%>
# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.
+<% end -%>
<% if options.dev? || options.edge? -%>
# Your gem is dependent on dev or edge Rails. Once you can lock this
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 743036362e..1369140537 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
-<% if full? && !options[:skip_active_record] -%>
+<% if full? && !options[:skip_active_record] && !options[:skip_test_unit] -%>
APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'
<% end %>
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 5cd31bf2ee..b3e74f9b02 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -1,7 +1,7 @@
<% if namespaced? -%>
-require "<%= namespaced_file_path %>/application_controller"
-<% end -%>
+require_dependency "<%= namespaced_file_path %>/application_controller"
+<% end -%>
<% module_namespacing do -%>
class <%= controller_class_name %>Controller < ApplicationController
# GET <%= route_url %>
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index ff9cf0087e..2ff340755a 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -37,8 +37,16 @@ module Rails
self.current_path = File.expand_path(Dir.pwd)
self.default_arguments = []
- setup :destination_root_is_set?, :ensure_current_path
- teardown :ensure_current_path
+ def setup
+ destination_root_is_set?
+ ensure_current_path
+ super
+ end
+
+ def teardown
+ ensure_current_path
+ super
+ end
# Sets which generator should be tested:
#
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 6b4bdb2921..5081074395 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -1,15 +1,33 @@
+require 'rails/application/route_inspector'
+
class Rails::InfoController < ActionController::Base
+ self.view_paths = File.join(File.dirname(__FILE__), 'templates')
+ layout 'application'
+
+ before_filter :require_local!
+
+ def index
+ redirect_to '/rails/info/routes'
+ end
+
def properties
- if consider_all_requests_local? || request.local?
- render :inline => Rails::Info.to_html
- else
- render :text => '<p>For security purposes, this information is only available to local requests.</p>', :status => :forbidden
- end
+ @info = Rails::Info.to_html
+ end
+
+ def routes
+ inspector = Rails::Application::RouteInspector.new
+ @info = inspector.format(_routes.routes).join("\n")
end
protected
- def consider_all_requests_local?
- Rails.application.config.consider_all_requests_local
+ def require_local!
+ unless local_request?
+ render :text => '<p>For security purposes, this information is only available to local requests.</p>', :status => :forbidden
+ end
+ end
+
+ def local_request?
+ Rails.application.config.consider_all_requests_local || request.local?
end
end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index b787d91821..6cd9c7bc95 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -1,5 +1,3 @@
-require "pathname"
-
module Rails
module Paths
# This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.
@@ -186,7 +184,7 @@ module Rails
raise "You need to set a path root" unless @root.path
map do |p|
- Pathname.new(@root.path).join(p)
+ File.join @root.path, p
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 2102f8a03c..a06be59759 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -145,6 +145,12 @@ module Rails
@load_console
end
+ def runner(&blk)
+ @load_runner ||= []
+ @load_runner << blk if blk
+ @load_runner
+ end
+
def generators(&blk)
@generators ||= []
@generators << blk if blk
@@ -179,8 +185,13 @@ module Rails
self.class.console.each { |block| block.call(app) }
end
+ def load_runner(app=self)
+ self.class.runner.each { |block| block.call(app) }
+ end
+
def load_tasks(app=self)
- extend Rake::DSL if defined? Rake::DSL
+ require 'rake'
+ extend Rake::DSL
self.class.rake_tasks.each { |block| self.instance_exec(app, &block) }
# load also tasks from all superclasses
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 4ec49eee76..2851ca4189 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -2,7 +2,7 @@ require 'rdoc/task'
# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
class RDocTaskWithoutDescriptions < RDoc::Task
- include ::Rake::DSL if defined?(::Rake::DSL)
+ include ::Rake::DSL
def define
task rdoc_task_name
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index eea8abe7d2..70370be3f5 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -60,7 +60,7 @@ namespace :db do
end
def find_engine_path(path)
- return if path == "/"
+ return File.expand_path(Dir.pwd) if path == "/"
if Rails::Engine.find(path)
path
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 206ce39773..f9cff5b627 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -20,7 +20,7 @@ namespace :rails do
project_templates = "#{Rails.root}/lib/templates"
default_templates = { "erb" => %w{controller mailer scaffold},
- "rails" => %w{controller helper scaffold_controller stylesheets} }
+ "rails" => %w{controller helper scaffold_controller assets} }
default_templates.each do |type, names|
local_template_type_dir = File.join(project_templates, type)
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 7dc54144da..5778b22f18 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,8 +1,6 @@
desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task :routes => :environment do
- Rails.application.reload_routes!
all_routes = Rails.application.routes.routes
-
require 'rails/application/route_inspector'
inspector = Rails::Application::RouteInspector.new
puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n"
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index f684e71267..67a6d2d2ac 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -3,6 +3,7 @@ STATS_DIRECTORIES = [
%w(Helpers app/helpers),
%w(Models app/models),
%w(Mailers app/mailers),
+ %w(Javascripts app/assets/javascripts),
%w(Libraries lib/),
%w(APIs app/apis),
%w(Integration\ tests test/integration),
diff --git a/railties/lib/rails/templates/layouts/application.html.erb b/railties/lib/rails/templates/layouts/application.html.erb
new file mode 100644
index 0000000000..53276d3e7c
--- /dev/null
+++ b/railties/lib/rails/templates/layouts/application.html.erb
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8" />
+ <title>Routes</title>
+ <style>
+ body { background-color: #fff; color: #333; }
+
+ body, p, ol, ul, td {
+ font-family: helvetica, verdana, arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ }
+
+ pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+ white-space: pre-wrap;
+ }
+
+ a { color: #000; }
+ a:visited { color: #666; }
+ a:hover { color: #fff; background-color:#000; }
+ </style>
+</head>
+<body>
+<h2>Your App: <%= link_to 'properties', '/rails/info/properties' %> | <%= link_to 'routes', '/rails/info/routes' %></h2>
+<%= yield %>
+
+</body>
+</html>
diff --git a/railties/lib/rails/templates/rails/info/properties.html.erb b/railties/lib/rails/templates/rails/info/properties.html.erb
new file mode 100644
index 0000000000..d47cbab202
--- /dev/null
+++ b/railties/lib/rails/templates/rails/info/properties.html.erb
@@ -0,0 +1 @@
+<%= @info.html_safe %> \ No newline at end of file
diff --git a/railties/lib/rails/templates/rails/info/routes.html.erb b/railties/lib/rails/templates/rails/info/routes.html.erb
new file mode 100644
index 0000000000..890f6f5b03
--- /dev/null
+++ b/railties/lib/rails/templates/rails/info/routes.html.erb
@@ -0,0 +1,9 @@
+<h2>
+ Routes
+</h2>
+
+<p>
+ Routes match in priority from top to bottom
+</p>
+
+<p><pre><%= @info %></pre></p> \ No newline at end of file
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 55bebb549b..0de4afe905 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -87,7 +87,7 @@ namespace :test do
if File.directory?(".svn")
changed_since_checkin = silence_stderr { `svn status` }.split.map { |path| path.chomp[7 .. -1] }
elsif File.directory?(".git")
- changed_since_checkin = silence_stderr { `git ls-files --modified --others` }.split.map { |path| path.chomp }
+ changed_since_checkin = silence_stderr { `git ls-files --modified --others --exclude-standard` }.split.map { |path| path.chomp }
else
abort "Not a Subversion or Git checkout."
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 7067253279..207739c4bc 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -7,6 +7,7 @@ Gem::Specification.new do |s|
s.summary = 'Tools for creating, working with, and running Rails applications.'
s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.'
s.required_ruby_version = '>= 1.9.3'
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
@@ -21,11 +22,7 @@ Gem::Specification.new do |s|
s.rdoc_options << '--exclude' << '.'
s.add_dependency('rake', '>= 0.8.7')
-
- # The current API of the Thor gem (0.14) will remain stable at least until Thor 2.0. Because
- # Thor is so heavily used by other gems, we will accept Thor's semver guarantee to reduce
- # the possibility of conflicts.
- s.add_dependency('thor', '>= 0.14.6', '< 2.0')
+ s.add_dependency('thor', '>= 0.15.3', '< 2.0')
s.add_dependency('rdoc', '~> 3.4')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index e23a19d69c..4243a79b58 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -17,9 +17,21 @@ module ApplicationTests
teardown_app
end
- def precompile!
+ def precompile!(env = nil)
quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:precompile` }
+ precompile_task = 'bundle exec rake assets:precompile'
+ precompile_task += ' ' + env if env
+ out = Dir.chdir(app_path){ %x[ #{precompile_task} ] }
+ assert $?.exitstatus == 0,
+ "#{precompile_task} has failed: #{out}.\
+ Probably you didn't install JavaScript runtime."
+ return out
+ end
+ end
+
+ def clean_assets!
+ quietly do
+ assert Dir.chdir(app_path){ system('bundle exec rake assets:clean') }
end
end
@@ -253,9 +265,8 @@ module ApplicationTests
# digest is default in false, we must enable it for test environment
add_to_env_config "test", "config.assets.digest = true"
- quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:precompile RAILS_ENV=test` }
- end
+ precompile!('RAILS_ENV=test')
+
file = Dir["#{app_path}/public/assets/application.css"].first
assert_match(/\/assets\/rails\.png/, File.read(file))
file = Dir["#{app_path}/public/assets/application-*.css"].first
@@ -285,9 +296,9 @@ module ApplicationTests
add_to_config "config.assets.compile = true"
ENV["RAILS_ENV"] = nil
- quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:precompile RAILS_GROUPS=assets` }
- end
+
+ precompile!('RAILS_GROUPS=assets')
+
file = Dir["#{app_path}/public/assets/application-*.css"].first
assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file))
end
@@ -310,9 +321,7 @@ module ApplicationTests
app_file "public/assets/application.css", "a { color: green; }"
app_file "public/assets/subdir/broken.png", "not really an image file"
- quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:clean` }
- end
+ clean_assets!
files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/*"]
assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}"
@@ -440,9 +449,8 @@ module ApplicationTests
add_to_config "config.assets.compile = true"
add_to_config "config.assets.digest = true"
- quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:clean assets:precompile` }
- end
+ clean_assets!
+ precompile!
files = Dir["#{app_path}/public/assets/application-*.js"]
assert_equal 1, files.length, "Expected digested application.js asset to be generated, but none found"
@@ -453,9 +461,8 @@ module ApplicationTests
add_to_env_config "production", "config.assets.prefix = 'production_assets'"
ENV["RAILS_ENV"] = nil
- quietly do
- Dir.chdir(app_path){ `bundle exec rake assets:clean` }
- end
+
+ clean_assets!
files = Dir["#{app_path}/public/production_assets/application.js"]
assert_equal 0, files.length, "Expected application.js asset to be removed, but still exists"
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 252dd0e31a..c1aa98e481 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -41,6 +41,21 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
+ test "a renders exception on pending migration" do
+ add_to_config <<-RUBY
+ config.active_record.migration_error = :page_load
+ config.consider_all_requests_local = true
+ config.action_dispatch.show_exceptions = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+ ActiveRecord::Migrator.stubs(:needs_migration?).returns(true)
+
+ get "/foo"
+ assert_equal 500, last_response.status
+ assert_match "ActiveRecord::PendingMigrationError", last_response.body
+ end
+
test "multiple queue construction is possible" do
require 'rails'
require "#{app_path}/config/environment"
@@ -133,6 +148,15 @@ module ApplicationTests
assert AppTemplate::Application.config.allow_concurrency
end
+ test "initialize a threadsafe app" do
+ add_to_config <<-RUBY
+ config.threadsafe!
+ RUBY
+
+ require "#{app_path}/config/application"
+ assert AppTemplate::Application.initialize!
+ end
+
test "asset_path defaults to nil for application" do
require "#{app_path}/config/environment"
assert_equal nil, AppTemplate::Application.config.asset_path
@@ -164,7 +188,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert !ActionController.autoload?(:Caching)
+ assert !ActionView.autoload?(:AssetPaths)
end
test "filter_parameters should be able to set via config.filter_parameters" do
@@ -288,16 +312,16 @@ module ApplicationTests
params = {:authenticity_token => token}
get "/posts/1"
- assert_match /patch/, last_response.body
+ assert_match(/patch/, last_response.body)
patch "/posts/1", params
- assert_match /update/, last_response.body
+ assert_match(/update/, last_response.body)
patch "/posts/1", params
assert_equal 200, last_response.status
put "/posts/1", params
- assert_match /update/, last_response.body
+ assert_match(/update/, last_response.body)
put "/posts/1", params
assert_equal 200, last_response.status
@@ -359,9 +383,10 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert_equal ActiveModel::MassAssignmentSecurity::WhiteList,
- ActiveRecord::Base.active_authorizers[:default].class
- assert_equal [""], ActiveRecord::Base.active_authorizers[:default].to_a
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, klass.active_authorizers[:default].class
+ assert_equal [], klass.active_authorizers[:default].to_a
end
test "registers interceptors with ActionMailer" do
@@ -602,5 +627,26 @@ module ApplicationTests
make_basic_app
assert app.config.colorize_logging
end
+
+ test "config.active_record.observers" do
+ add_to_config <<-RUBY
+ config.active_record.observers = :foo_observer
+ RUBY
+
+ app_file 'app/models/foo.rb', <<-RUBY
+ class Foo < ActiveRecord::Base
+ end
+ RUBY
+
+ app_file 'app/models/foo_observer.rb', <<-RUBY
+ class FooObserver < ActiveRecord::Observer
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ ActiveRecord::Base
+ assert defined?(FooObserver)
+ end
end
end
diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb
index a77c6f472c..46fd09cb26 100644
--- a/railties/test/application/rack/logger_test.rb
+++ b/railties/test/application/rack/logger_test.rb
@@ -5,6 +5,7 @@ require "rack/test"
module ApplicationTests
module RackTests
class LoggerTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
include ActiveSupport::LogSubscriber::TestHelper
include Rack::Test::Methods
@@ -17,6 +18,7 @@ module ApplicationTests
end
def teardown
+ super
teardown_app
end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 05d73dfc5c..27de75b63b 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -3,12 +3,16 @@ require "isolation/abstract_unit"
module ApplicationTests
module RakeTests
class RakeNotesTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
def setup
build_app
require "rails/all"
+ super
end
def teardown
+ super
teardown_app
end
@@ -33,14 +37,14 @@ module ApplicationTests
output = `bundle exec rake notes`
lines = output.scan(/\[([0-9\s]+)\](\s)/)
- assert_match /note in erb/, output
- assert_match /note in haml/, output
- assert_match /note in slim/, output
- assert_match /note in ruby/, output
- assert_match /note in coffee/, output
- assert_match /note in js/, output
- assert_match /note in css/, output
- assert_match /note in scss/, output
+ assert_match(/note in erb/, output)
+ assert_match(/note in haml/, output)
+ assert_match(/note in slim/, output)
+ assert_match(/note in ruby/, output)
+ assert_match(/note in coffee/, output)
+ assert_match(/note in js/, output)
+ assert_match(/note in css/, output)
+ assert_match(/note in scss/, output)
assert_equal 8, lines.size
@@ -72,12 +76,12 @@ module ApplicationTests
output = `bundle exec rake notes`
lines = output.scan(/\[([0-9\s]+)\]/).flatten
- assert_match /note in app directory/, output
- assert_match /note in config directory/, output
- assert_match /note in lib directory/, output
- assert_match /note in script directory/, output
- assert_match /note in test directory/, output
- assert_no_match /note in some_other directory/, output
+ assert_match(/note in app directory/, output)
+ assert_match(/note in config directory/, output)
+ assert_match(/note in lib directory/, output)
+ assert_match(/note in script directory/, output)
+ assert_match(/note in test directory/, output)
+ assert_no_match(/note in some_other directory/, output)
assert_equal 5, lines.size
@@ -108,13 +112,13 @@ module ApplicationTests
output = `SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes`
lines = output.scan(/\[([0-9\s]+)\]/).flatten
- assert_match /note in app directory/, output
- assert_match /note in config directory/, output
- assert_match /note in lib directory/, output
- assert_match /note in script directory/, output
- assert_match /note in test directory/, output
+ assert_match(/note in app directory/, output)
+ assert_match(/note in config directory/, output)
+ assert_match(/note in lib directory/, output)
+ assert_match(/note in script directory/, output)
+ assert_match(/note in test directory/, output)
- assert_match /note in some_other directory/, output
+ assert_match(/note in some_other directory/, output)
assert_equal 6, lines.size
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 27d521485c..e0ee349550 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -122,6 +122,18 @@ module ApplicationTests
assert_equal 0, ::AppTemplate::Application::User.count
end
+ def test_loading_only_yml_fixtures
+ Dir.chdir(app_path) do
+ `rake db:migrate`
+ end
+
+ app_file "test/fixtures/products.csv", ""
+
+ require "#{rails_root}/config/environment"
+ errormsg = Dir.chdir(app_path) { `rake db:fixtures:load` }
+ assert $?.success?, errormsg
+ end
+
def test_scaffold_tests_pass_by_default
output = Dir.chdir(app_path) do
`rails generate scaffold user username:string password:string;
@@ -167,5 +179,39 @@ module ApplicationTests
end
assert !File.exists?(File.join(app_path, 'db', 'schema_cache.dump'))
end
+
+ def test_load_activerecord_base_when_we_use_observers
+ Dir.chdir(app_path) do
+ `bundle exec rails g model user;
+ bundle exec rake db:migrate;
+ bundle exec rails g observer user;`
+
+ add_to_config "config.active_record.observers = :user_observer"
+
+ assert_equal "0", `bundle exec rails r "puts User.count"`.strip
+
+ app_file "lib/tasks/count_user.rake", <<-RUBY
+ namespace :user do
+ task :count => :environment do
+ puts User.count
+ end
+ end
+ RUBY
+
+ assert_equal "0", `bundle exec rake user:count`.strip
+ end
+ end
+
+ def test_copy_templates
+ Dir.chdir(app_path) do
+ `bundle exec rake rails:templates:copy`
+ %w(controller mailer scaffold).each do |dir|
+ assert File.exists?(File.join(app_path, 'lib', 'templates', 'erb', dir))
+ end
+ %w(controller helper scaffold_controller assets).each do |dir|
+ assert File.exists?(File.join(app_path, 'lib', 'templates', 'rails', dir))
+ end
+ end
+ end
end
end
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index 977a5fc7e8..d1373ba202 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -15,12 +15,24 @@ module ApplicationTests
teardown_app
end
+ test "rails/info/routes in development" do
+ app("development")
+ get "/rails/info/routes"
+ assert_equal 200, last_response.status
+ end
+
test "rails/info/properties in development" do
app("development")
get "/rails/info/properties"
assert_equal 200, last_response.status
end
+ test "rails/info/routes in production" do
+ app("production")
+ get "/rails/info/routes"
+ assert_equal 404, last_response.status
+ end
+
test "rails/info/properties in production" do
app("production")
get "/rails/info/properties"
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index e1d283a7fd..81ed5873a5 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -57,5 +57,15 @@ module ApplicationTests
assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` }
end
+
+ def test_with_hook
+ add_to_config <<-RUBY
+ runner do |app|
+ app.config.ran = true
+ end
+ RUBY
+
+ assert_match "true", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.application.config.ran"` }
+ end
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 9aa1d68675..91ede1cb68 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -3,47 +3,44 @@ require 'rails/commands/console'
class Rails::ConsoleTest < ActiveSupport::TestCase
class FakeConsole
+ def self.start; end
end
def setup
end
def test_sandbox_option
- console = Rails::Console.new(app, ["--sandbox"])
+ console = Rails::Console.new(app, parse_arguments(["--sandbox"]))
assert console.sandbox?
end
def test_short_version_of_sandbox_option
- console = Rails::Console.new(app, ["-s"])
+ console = Rails::Console.new(app, parse_arguments(["-s"]))
assert console.sandbox?
end
def test_debugger_option
- console = Rails::Console.new(app, ["--debugger"])
+ console = Rails::Console.new(app, parse_arguments(["--debugger"]))
assert console.debugger?
end
def test_no_options
- console = Rails::Console.new(app, [])
+ console = Rails::Console.new(app, parse_arguments([]))
assert !console.debugger?
assert !console.sandbox?
end
def test_start
- app.expects(:sandbox=).with(nil)
FakeConsole.expects(:start)
-
start
-
- assert_match /Loading \w+ environment \(Rails/, output
+ assert_match(/Loading \w+ environment \(Rails/, output)
end
def test_start_with_debugger
- app.expects(:sandbox=).with(nil)
+ rails_console = Rails::Console.new(app, parse_arguments(["--debugger"]))
rails_console.expects(:require_debugger).returns(nil)
- FakeConsole.expects(:start)
- start ["--debugger"]
+ silence_stream(STDOUT) { rails_console.start }
end
def test_start_with_sandbox
@@ -52,46 +49,67 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
start ["--sandbox"]
- assert_match /Loading \w+ environment in sandbox \(Rails/, output
+ assert_match(/Loading \w+ environment in sandbox \(Rails/, output)
end
def test_console_with_environment
- app.expects(:sandbox=).with(nil)
- FakeConsole.expects(:start)
-
start ["-e production"]
+ assert_match(/\sproduction\s/, output)
+ end
+
+ def test_console_defaults_to_IRB
+ config = mock("config", :console => nil)
+ app = mock("app", :config => config)
+ app.expects(:load_console).returns(nil)
- assert_match /production/, output
+ assert_equal IRB, Rails::Console.new(app).console
end
- def test_console_with_rails_environment
- app.expects(:sandbox=).with(nil)
- FakeConsole.expects(:start)
+ def test_default_environment_with_no_rails_env
+ with_rails_env nil do
+ start
+ assert_match /\sdevelopment\s/, output
+ end
+ end
- start ["RAILS_ENV=production"]
+ def test_default_environment_with_rails_env
+ with_rails_env 'special-production' do
+ start
+ assert_match /\sspecial-production\s/, output
+ end
+ end
+
+ def test_e_option
+ start ['-e', 'special-production']
+ assert_match /\sspecial-production\s/, output
+ end
- assert_match /production/, output
+ def test_environment_option
+ start ['--environment=special-production']
+ assert_match /\sspecial-production\s/, output
end
+ def test_rails_env_is_production_when_first_argument_is_p
+ start ['p']
+ assert_match /\sproduction\s/, output
+ end
- def test_console_defaults_to_IRB
- config = mock("config", :console => nil)
- app = mock("app", :config => config)
- app.expects(:load_console).returns(nil)
+ def test_rails_env_is_test_when_first_argument_is_t
+ start ['t']
+ assert_match /\stest\s/, output
+ end
- assert_equal IRB, Rails::Console.new(app).console
+ def test_rails_env_is_development_when_argument_is_d
+ start ['d']
+ assert_match /\sdevelopment\s/, output
end
private
attr_reader :output
- def rails_console
- @rails_console ||= Rails::Console.new(app)
- end
-
def start(argv = [])
- rails_console.stubs(:arguments => argv)
+ rails_console = Rails::Console.new(app, parse_arguments(argv))
@output = output = capture(:stdout) { rails_console.start }
end
@@ -99,8 +117,21 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
@app ||= begin
config = mock("config", :console => FakeConsole)
app = mock("app", :config => config)
+ app.stubs(:sandbox=).returns(nil)
app.expects(:load_console)
app
end
end
+
+ def parse_arguments(args)
+ Rails::Console.parse_arguments(args)
+ end
+
+ def with_rails_env(env)
+ original_rails_env = ENV['RAILS_ENV']
+ ENV['RAILS_ENV'] = env
+ yield
+ ensure
+ ENV['RAILS_ENV'] = original_rails_env
+ end
end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
index 85a7edfacd..d45bdaabf5 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -10,35 +10,37 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
Rails::DBConsole.const_set(:APP_PATH, "erb")
app_config({})
- capture_abort { Rails::DBConsole.config }
+ capture_abort { Rails::DBConsole.new.config }
assert aborted
- assert_match /No database is configured for the environment '\w+'/, output
+ assert_match(/No database is configured for the environment '\w+'/, output)
app_config(test: "with_init")
- assert_equal Rails::DBConsole.config, "with_init"
+ assert_equal Rails::DBConsole.new.config, "with_init"
app_db_file("test:\n without_init")
- assert_equal Rails::DBConsole.config, "without_init"
+ assert_equal Rails::DBConsole.new.config, "without_init"
app_db_file("test:\n <%= Rails.something_app_specific %>")
- assert_equal Rails::DBConsole.config, "with_init"
+ assert_equal Rails::DBConsole.new.config, "with_init"
app_db_file("test:\n\ninvalid")
- assert_equal Rails::DBConsole.config, "with_init"
+ assert_equal Rails::DBConsole.new.config, "with_init"
end
def test_env
- assert_equal Rails::DBConsole.env, "test"
+ assert_equal Rails::DBConsole.new.environment, "test"
+
+ ENV['RAILS_ENV'] = nil
+ ENV['RACK_ENV'] = nil
Rails.stubs(:respond_to?).with(:env).returns(false)
- assert_equal Rails::DBConsole.env, "test"
+ assert_equal Rails::DBConsole.new.environment, "development"
- ENV['RAILS_ENV'] = nil
ENV['RACK_ENV'] = "rack_env"
- assert_equal Rails::DBConsole.env, "rack_env"
+ assert_equal Rails::DBConsole.new.environment, "rack_env"
ENV['RAILS_ENV'] = "rails_env"
- assert_equal Rails::DBConsole.env, "rails_env"
+ assert_equal Rails::DBConsole.new.environment, "rails_env"
ensure
ENV['RAILS_ENV'] = "test"
end
@@ -92,20 +94,25 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
end
def test_sqlite3
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', 'db')
- start(adapter: 'sqlite3', database: 'db')
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', Rails.root.join('db.sqlite3').to_s)
+ start(adapter: 'sqlite3', database: 'db.sqlite3')
assert !aborted
end
def test_sqlite3_mode
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', 'db')
- start({adapter: 'sqlite3', database: 'db'}, ['--mode', 'html'])
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', Rails.root.join('db.sqlite3').to_s)
+ start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--mode', 'html'])
assert !aborted
end
def test_sqlite3_header
- dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', 'db')
- start({adapter: 'sqlite3', database: 'db'}, ['--header'])
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', Rails.root.join('db.sqlite3').to_s)
+ start({adapter: 'sqlite3', database: 'db.sqlite3'}, ['--header'])
+ end
+
+ def test_sqlite3_db_absolute_path
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '/tmp/db.sqlite3')
+ start(adapter: 'sqlite3', database: '/tmp/db.sqlite3')
assert !aborted
end
@@ -124,7 +131,25 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
def test_unknown_command_line_client
start(adapter: 'unknown', database: 'db')
assert aborted
- assert_match /Unknown command-line client for db/, output
+ assert_match(/Unknown command-line client for db/, output)
+ end
+
+ def test_print_help_short
+ stdout = capture(:stdout) do
+ start({}, ['-h'])
+ end
+ assert aborted
+ assert_equal '', output
+ assert_match(/Usage:.*dbconsole/, stdout)
+ end
+
+ def test_print_help_long
+ stdout = capture(:stdout) do
+ start({}, ['--help'])
+ end
+ assert aborted
+ assert_equal '', output
+ assert_match(/Usage:.*dbconsole/, stdout)
end
private
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 8039aec873..4a3ea82e3d 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -4,14 +4,14 @@ require 'rails/commands/server'
class Rails::ServerTest < ActiveSupport::TestCase
def test_environment_with_server_option
- args = ["thin", "RAILS_ENV=production"]
+ args = ["thin", "-e", "production"]
options = Rails::Server::Options.new.parse!(args)
assert_equal 'production', options[:environment]
assert_equal 'thin', options[:server]
end
def test_environment_without_server_option
- args = ["RAILS_ENV=production"]
+ args = ["-e", "production"]
options = Rails::Server::Options.new.parse!(args)
assert_equal 'production', options[:environment]
assert_nil options[:server]
diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb
new file mode 100644
index 0000000000..5984c0b425
--- /dev/null
+++ b/railties/test/configuration/middleware_stack_proxy_test.rb
@@ -0,0 +1,59 @@
+require 'minitest/autorun'
+require 'rails/configuration'
+require 'active_support/test_case'
+
+module Rails
+ module Configuration
+ class MiddlewareStackProxyTest < ActiveSupport::TestCase
+ def setup
+ @stack = MiddlewareStackProxy.new
+ end
+
+ def test_playback_insert_before
+ @stack.insert_before :foo
+ assert_playback :insert_before, :foo
+ end
+
+ def test_playback_insert_after
+ @stack.insert_after :foo
+ assert_playback :insert_after, :foo
+ end
+
+ def test_playback_swap
+ @stack.swap :foo
+ assert_playback :swap, :foo
+ end
+
+ def test_playback_use
+ @stack.use :foo
+ assert_playback :use, :foo
+ end
+
+ def test_playback_delete
+ @stack.delete :foo
+ assert_playback :delete, :foo
+ end
+
+ def test_order
+ @stack.swap :foo
+ @stack.delete :foo
+
+ mock = MiniTest::Mock.new
+ mock.expect :send, nil, [:swap, :foo]
+ mock.expect :send, nil, [:delete, :foo]
+
+ @stack.merge_into mock
+ mock.verify
+ end
+
+ private
+
+ def assert_playback(msg_name, args)
+ mock = MiniTest::Mock.new
+ mock.expect :send, nil, [msg_name, args]
+ @stack.merge_into(mock)
+ mock.verify
+ end
+ end
+ end
+end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index a8c8fcd5b7..bc086c5986 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -67,6 +67,14 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'Gemfile', /^gem "rspec-rails"$/
end
+ def test_gem_should_include_options
+ run_generator
+
+ action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1'
+
+ assert_file 'Gemfile', /gem "rspec", github: "dchelimsky\/rspec", tag: "1\.2\.9\.rc1"/
+ end
+
def test_gem_group_should_wrap_gems_in_a_group
run_generator
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 26e912fd9e..5534476a6d 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -246,7 +246,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_gem "therubyrhino"
else
- assert_file "Gemfile", /# gem\s+["']therubyracer["']+, platform: :ruby$/
+ assert_file "Gemfile", /# gem\s+["']therubyracer["']+, platforms: :ruby$/
end
end
diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb
index d6338bd3da..a2b94f2e50 100644
--- a/railties/test/generators/assets_generator_test.rb
+++ b/railties/test/generators/assets_generator_test.rb
@@ -1,7 +1,6 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/assets/assets_generator'
-# FIXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub
class AssetsGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
arguments %w(posts)
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 903465392d..2ae9dc61a7 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -21,7 +21,7 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
def test_namespaced_controller_skeleton_is_created
run_generator
assert_file "app/controllers/test_app/account_controller.rb",
- /require "test_app\/application_controller"/,
+ /require_dependency "test_app\/application_controller"/,
/module TestApp/,
/ class AccountController < ApplicationController/
@@ -38,7 +38,9 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
def test_namespaced_controller_with_additional_namespace
run_generator ["admin/account"]
- assert_file "app/controllers/test_app/admin/account_controller.rb", /module TestApp/, / class Admin::AccountController < ApplicationController/
+ assert_file "app/controllers/test_app/admin/account_controller.rb", /module TestApp/, / class Admin::AccountController < ApplicationController/ do |contents|
+ assert_match %r(require_dependency "test_app/application_controller"), contents
+ end
end
def test_helpr_is_also_namespaced
@@ -234,7 +236,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase
# Controller
assert_file "app/controllers/test_app/product_lines_controller.rb",
- /require "test_app\/application_controller"/,
+ /require_dependency "test_app\/application_controller"/,
/module TestApp/,
/class ProductLinesController < ApplicationController/
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 51374e5f3f..0a235b56d5 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -58,6 +58,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/
end
+ def test_generating_test_files_in_full_mode_without_unit_test_files
+ run_generator [destination_root, "-T", "--full"]
+
+ assert_no_directory "test/integration/"
+ assert_no_file "test"
+ assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
+ end
+
def test_ensure_that_plugin_options_are_not_passed_to_app_generator
FileUtils.cd(Rails.root)
assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]))
@@ -82,6 +90,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "bukkits.gemspec", /mysql/
end
+ def test_dont_generate_development_dependency
+ run_generator [destination_root, "--skip-active-record"]
+
+ assert_file "bukkits.gemspec" do |contents|
+ assert_no_match(/s.add_development_dependency "sqlite3"/, contents)
+ end
+ end
+
def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
run_generator [destination_root, "--skip-active-record"]
assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
@@ -269,6 +285,32 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_skipping_gemspec
run_generator [destination_root, "--skip-gemspec"]
assert_no_file "bukkits.gemspec"
+ assert_file "Gemfile" do |contents|
+ assert_no_match('gemspec', contents)
+ assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents)
+ assert_match(/group :development do\n gem "sqlite3"\nend/, contents)
+ assert_no_match(/# gem "jquery-rails"/, contents)
+ end
+ end
+
+ def test_skipping_gemspec_in_full_mode
+ run_generator [destination_root, "--skip-gemspec", "--full"]
+ assert_no_file "bukkits.gemspec"
+ assert_file "Gemfile" do |contents|
+ assert_no_match('gemspec', contents)
+ assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents)
+ assert_match(/group :development do\n gem "sqlite3"\nend/, contents)
+ assert_match(/# gem "jquery-rails"/, contents)
+ assert_no_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents)
+ end
+ end
+
+ def test_skipping_gemspec_in_full_mode_with_javascript_option
+ run_generator [destination_root, "--skip-gemspec", "--full", "--javascript=prototype"]
+ assert_file "Gemfile" do |contents|
+ assert_match(/# gem "prototype-rails"/, contents)
+ assert_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents)
+ end
end
def test_creating_plugin_in_app_directory_adds_gemfile_entry
@@ -279,7 +321,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root]
- assert_file gemfile_path, /gem 'bukkits', :path => 'tmp\/bukkits'/
+ assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/
ensure
Object.send(:remove_const, 'APP_PATH')
FileUtils.rm gemfile_path
@@ -294,7 +336,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-gemfile-entry"]
assert_file gemfile_path do |contents|
- assert_no_match(/gem 'bukkits', :path => 'tmp\/bukkits'/, contents)
+ assert_no_match(/gem 'bukkits', path: 'tmp\/bukkits'/, contents)
end
ensure
Object.send(:remove_const, 'APP_PATH')
@@ -303,12 +345,10 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
protected
-
def action(*args, &block)
silence(:stdout){ generator.send(*args, &block) }
end
-protected
def default_files
::DEFAULT_PLUGIN_FILES
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 60e7e57a91..417d019178 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -186,7 +186,7 @@ class GeneratorsTest < Rails::Generators::TestCase
mkdir_p(File.dirname(template))
File.open(template, 'w'){ |f| f.write "empty" }
- output = capture(:stdout) do
+ capture(:stdout) do
Rails::Generators.invoke :model, ["user"], :destination_root => destination_root
end
@@ -205,7 +205,7 @@ class GeneratorsTest < Rails::Generators::TestCase
def test_usage_with_embedded_ruby
require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__))
output = capture(:stdout) { Rails::Generators.invoke :usage_template, ['--help'] }
- assert_match /:: 2 ::/, output
+ assert_match(/:: 2 ::/, output)
end
def test_hide_namespace
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 03be81e59f..800b1c90f0 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -19,15 +19,17 @@ RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
# to run the tests
require "active_support/testing/isolation"
require "active_support/core_ext/kernel/reporting"
+require 'tmpdir'
module TestHelpers
module Paths
- module_function
-
- TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp]))
+ def app_template_path
+ File.join Dir.tmpdir, 'app_template'
+ end
def tmp_path(*args)
- File.join(TMP_PATH, *args)
+ @tmp_path ||= File.realpath(Dir.mktmpdir)
+ File.join(@tmp_path, *args)
end
def app_path(*args)
@@ -95,7 +97,7 @@ module TestHelpers
ENV['RAILS_ENV'] = 'development'
FileUtils.rm_rf(app_path)
- FileUtils.cp_r(tmp_path('app_template'), app_path)
+ FileUtils.cp_r(app_template_path, app_path)
# Delete the initializers unless requested
unless options[:initializers]
@@ -272,18 +274,18 @@ end
Module.new do
extend TestHelpers::Paths
# Build a rails app
- if File.exist?(tmp_path)
- FileUtils.rm_rf(tmp_path)
+ if File.exist?(app_template_path)
+ FileUtils.rm_rf(app_template_path)
end
- FileUtils.mkdir(tmp_path)
+ FileUtils.mkdir(app_template_path)
environment = File.expand_path('../../../../load_paths', __FILE__)
if File.exist?("#{environment}.rb")
require_environment = "-r #{environment}"
end
- `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{tmp_path('app_template')}`
- File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f|
+ `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path}`
+ File.open("#{app_template_path}/config/boot.rb", 'w') do |f|
if require_environment
f.puts "Dir.chdir('#{File.dirname(environment)}') do"
f.puts " require '#{environment}'"
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index aa04cad033..5d6b6f9f72 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -29,7 +29,7 @@ class PathsTest < ActiveSupport::TestCase
test "creating a root level path" do
@root.add "app"
assert_equal ["/foo/bar/app"], @root["app"].to_a
- assert_equal [Pathname.new("/foo/bar/app")], @root["app"].paths
+ assert_equal ["/foo/bar/app"], @root["app"].paths
end
test "creating a root level path with options" do
@@ -192,7 +192,7 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].glob = "*.rb"
assert_equal "*.rb", @root["app"].glob
- assert_equal [Pathname.new("/app")], @root["app"].paths
+ assert_equal ["/foo/bar/app"], @root["app"].paths
end
test "it should be possible to override a path's default glob without assignment" do
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index f7a30a16d2..cfb32b7d35 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -12,29 +12,28 @@ class InfoControllerTest < ActionController::TestCase
def setup
Rails.application.routes.draw do
get '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/routes' => "rails/info#routes"
end
- @request.stubs(:local? => true)
- @controller.stubs(:consider_all_requests_local? => false)
+ @controller.stubs(:local_request? => true)
@routes = Rails.application.routes
Rails::InfoController.send(:include, @routes.url_helpers)
end
test "info controller does not allow remote requests" do
- @request.stubs(:local? => false)
+ @controller.stubs(:local_request? => false)
get :properties
assert_response :forbidden
end
test "info controller renders an error message when request was forbidden" do
- @request.stubs(:local? => false)
+ @controller.stubs(:local_request? => false)
get :properties
assert_select 'p'
end
test "info controller allows requests when all requests are considered local" do
- @request.stubs(:local? => false)
- @controller.stubs(:consider_all_requests_local? => true)
+ @controller.stubs(:local_request? => true)
get :properties
assert_response :success
end
@@ -48,4 +47,10 @@ class InfoControllerTest < ActionController::TestCase
get :properties
assert_select 'table'
end
+
+ test "info controller renders with routes" do
+ get :routes
+ assert_select 'pre'
+ end
+
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 7a047ef93a..52c7fae6c6 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1009,9 +1009,7 @@ YAML
boot_rails
- methods = Bukkits::Engine.helpers.public_instance_methods.collect(&:to_s).sort
- expected = ["bar", "baz"]
- assert_equal expected, methods
+ assert_equal [:bar, :baz], Bukkits::Engine.helpers.public_instance_methods.sort
end
test "setting priority for engines with config.railties_order" do
@@ -1098,6 +1096,10 @@ YAML
get("/assets/bar.js")
assert_equal "// App's bar js\n;", last_response.body.strip
+
+ # ensure that railties are not added twice
+ railties = Rails.application.ordered_railties.map(&:class)
+ assert_equal railties, railties.uniq
end
test "railties_order adds :all with lowest priority if not given" do
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index 6ebbabc0ff..1938bfb6c2 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -8,11 +8,13 @@ module RailtiesTests
class GeneratorTest < Rails::Generators::TestCase
include ActiveSupport::Testing::Isolation
- TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp]))
- self.destination_root = File.join(TMP_PATH, "foo_bar")
+ def destination_root
+ tmp_path 'foo_bar'
+ end
def tmp_path(*args)
- File.join(TMP_PATH, *args)
+ @tmp_path ||= File.realpath(Dir.mktmpdir)
+ File.join(@tmp_path, *args)
end
def engine_path
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index cd495320b5..c80b0f63af 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -163,6 +163,22 @@ module RailtiesTest
assert $ran_block
end
+ test "runner block is executed when MyApp.load_runner is called" do
+ $ran_block = false
+
+ class MyTie < Rails::Railtie
+ runner do
+ $ran_block = true
+ end
+ end
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ AppTemplate::Application.load_runner
+ assert $ran_block
+ end
+
test "railtie can add initializers" do
$ran_block = false