aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--Gemfile6
-rw-r--r--README.rdoc8
-rw-r--r--RELEASING_RAILS.rdoc3
-rw-r--r--Rakefile6
-rw-r--r--actionmailer/CHANGELOG.md43
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/actionmailer.gemspec2
-rw-r--r--actionmailer/lib/action_mailer.rb3
-rw-r--r--actionmailer/lib/action_mailer/base.rb52
-rw-r--r--actionmailer/lib/action_mailer/collector.rb2
-rw-r--r--actionmailer/lib/action_mailer/queued_message.rb37
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb2
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb7
-rw-r--r--actionmailer/lib/rails/generators/mailer/USAGE5
-rw-r--r--actionmailer/test/abstract_unit.rb3
-rw-r--r--actionmailer/test/base_test.rb81
-rw-r--r--actionmailer/test/fixtures/base_test/after_action_mailer/welcome.html.erb (renamed from actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb)0
-rw-r--r--actionmailer/test/fixtures/base_test/before_action_mailer/welcome.html.erb (renamed from actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb)0
-rw-r--r--actionmailer/test/mailers/async_mailer.rb3
-rw-r--r--actionmailer/test/mailers/base_mailer.rb5
-rw-r--r--actionmailer/test/spec_type_test.rb37
-rw-r--r--actionmailer/test/test_test.rb144
-rw-r--r--actionpack/CHANGELOG.md300
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/Rakefile8
-rw-r--r--actionpack/actionpack.gemspec1
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb150
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb7
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb40
-rw-r--r--actionpack/lib/action_controller/caching.rb8
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb116
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb4
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb1
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb4
-rw-r--r--actionpack/lib/action_controller/metal/head.rb1
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb9
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb48
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb2
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb5
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb12
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb62
-rw-r--r--actionpack/lib/action_controller/railtie.rb17
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb1
-rw-r--r--actionpack/lib/action_controller/test_case.rb14
-rw-r--r--actionpack/lib/action_dispatch.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb10
-rw-r--r--actionpack/lib/action_dispatch/http/filter_redirect.rb37
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb13
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/backwards.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey/formatter.rb141
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb162
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/simulator.rb44
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb156
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/builder.rb76
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/dot.rb36
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/simulator.rb47
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb163
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb124
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb206
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y47
-rw-r--r--actionpack/lib/action_dispatch/journey/parser_extras.rb23
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb196
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb94
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb168
-rw-r--r--actionpack/lib/action_dispatch/journey/router/strexp.rb24
-rw-r--r--actionpack/lib/action_dispatch/journey/router/utils.rb54
-rw-r--r--actionpack/lib/action_dispatch/journey/routes.rb76
-rw-r--r--actionpack/lib/action_dispatch/journey/scanner.rb61
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb189
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/fsm.css34
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/fsm.js134
-rw-r--r--actionpack/lib/action_dispatch/journey/visualizer/index.html.erb52
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb66
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb19
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb36
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb166
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb25
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb125
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb47
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb52
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb16
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb56
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/inspector.rb86
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb87
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb21
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb12
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_view.rb2
-rw-r--r--actionpack/lib/action_view/digestor.rb23
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb146
-rw-r--r--actionpack/lib/action_view/helpers/asset_url_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb53
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb20
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb14
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb495
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb104
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb214
-rw-r--r--actionpack/lib/action_view/helpers/output_safety_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_helpers.rb3
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_select.rb4
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb78
-rw-r--r--actionpack/lib/action_view/lookup_context.rb3
-rw-r--r--actionpack/lib/action_view/railtie.rb2
-rw-r--r--actionpack/lib/action_view/record_identifier.rb2
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb7
-rw-r--r--actionpack/lib/action_view/renderer/renderer.rb14
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb2
-rw-r--r--actionpack/lib/action_view/template.rb1
-rw-r--r--actionpack/lib/action_view/template/error.rb20
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb11
-rw-r--r--actionpack/lib/action_view/template/resolver.rb61
-rw-r--r--actionpack/lib/action_view/template/types.rb1
-rw-r--r--actionpack/lib/action_view/test_case.rb3
-rw-r--r--actionpack/lib/action_view/vendor/html-scanner.rb2
-rw-r--r--actionpack/test/abstract/abstract_controller_test.rb8
-rw-r--r--actionpack/test/abstract/callbacks_test.rb92
-rw-r--r--actionpack/test/abstract/helper_test.rb10
-rw-r--r--actionpack/test/abstract_unit.rb3
-rw-r--r--actionpack/test/active_record_unit.rb18
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb22
-rw-r--r--actionpack/test/controller/assert_select_test.rb10
-rw-r--r--actionpack/test/controller/caching_test.rb12
-rw-r--r--actionpack/test/controller/default_url_options_with_before_action_test.rb (renamed from actionpack/test/controller/default_url_options_with_filter_test.rb)10
-rw-r--r--actionpack/test/controller/filters_test.rb49
-rw-r--r--actionpack/test/controller/flash_hash_test.rb21
-rw-r--r--actionpack/test/controller/flash_test.rb6
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb6
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb4
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb41
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb88
-rw-r--r--actionpack/test/controller/mime_responds_test.rb2
-rw-r--r--actionpack/test/controller/new_base/base_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_context_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb10
-rw-r--r--actionpack/test/controller/output_escaping_test.rb2
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_test.rb25
-rw-r--r--actionpack/test/controller/parameters/raise_on_unpermitted_parameters_test.rb33
-rw-r--r--actionpack/test/controller/render_test.rb68
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb2
-rw-r--r--actionpack/test/controller/rescue_test.rb4
-rw-r--r--actionpack/test/controller/routing_test.rb22
-rw-r--r--actionpack/test/controller/send_file_test.rb14
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb2
-rw-r--r--actionpack/test/controller/spec_style_test.rb208
-rw-r--r--actionpack/test/controller/spec_type_test.rb37
-rw-r--r--actionpack/test/controller/sweeper_test.rb16
-rw-r--r--actionpack/test/controller/test_case_test.rb14
-rw-r--r--actionpack/test/controller/view_paths_test.rb2
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb24
-rw-r--r--actionpack/test/dispatch/live_response_test.rb2
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb12
-rw-r--r--actionpack/test/dispatch/request/session_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb133
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb28
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb86
-rw-r--r--actionpack/test/dispatch/routing_test.rb1748
-rw-r--r--actionpack/test/dispatch/session/abstract_store_test.rb2
-rw-r--r--actionpack/test/dispatch/spec_type_test.rb41
-rw-r--r--actionpack/test/dispatch/ssl_test.rb9
-rw-r--r--actionpack/test/fixtures/digestor/messages/show.html.erb6
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb3
-rw-r--r--actionpack/test/journey/gtg/builder_test.rb79
-rw-r--r--actionpack/test/journey/gtg/transition_table_test.rb115
-rw-r--r--actionpack/test/journey/nfa/simulator_test.rb98
-rw-r--r--actionpack/test/journey/nfa/transition_table_test.rb72
-rw-r--r--actionpack/test/journey/nodes/symbol_test.rb17
-rw-r--r--actionpack/test/journey/path/pattern_test.rb284
-rw-r--r--actionpack/test/journey/route/definition/parser_test.rb110
-rw-r--r--actionpack/test/journey/route/definition/scanner_test.rb56
-rw-r--r--actionpack/test/journey/route_test.rb103
-rw-r--r--actionpack/test/journey/router/strexp_test.rb32
-rw-r--r--actionpack/test/journey/router/utils_test.rb21
-rw-r--r--actionpack/test/journey/router_test.rb575
-rw-r--r--actionpack/test/journey/routes_test.rb53
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb24
-rw-r--r--actionpack/test/template/atom_feed_helper_test.rb2
-rw-r--r--actionpack/test/template/digestor_test.rb6
-rw-r--r--actionpack/test/template/form_collections_helper_test.rb6
-rw-r--r--actionpack/test/template/form_helper_test.rb14
-rw-r--r--actionpack/test/template/form_options_helper_test.rb2
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb30
-rw-r--r--actionpack/test/template/render_test.rb5
-rw-r--r--actionpack/test/template/spec_type_test.rb39
-rw-r--r--actionpack/test/template/template_test.rb4
-rw-r--r--actionpack/test/template/test_test.rb56
-rw-r--r--actionpack/test/template/url_helper_test.rb56
-rw-r--r--actionpack/test/ts_isolated.rb2
-rw-r--r--activemodel/CHANGELOG.md40
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/examples/validations.rb6
-rw-r--r--activemodel/lib/active_model.rb4
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb13
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/conversion.rb2
-rw-r--r--activemodel/lib/active_model/dirty.rb6
-rw-r--r--activemodel/lib/active_model/errors.rb6
-rw-r--r--activemodel/lib/active_model/locale/en.yml1
-rw-r--r--activemodel/lib/active_model/naming.rb5
-rw-r--r--activemodel/lib/active_model/observer_array.rb152
-rw-r--r--activemodel/lib/active_model/observing.rb374
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--[-rwxr-xr-x]activemodel/lib/active_model/serializers/xml.rb14
-rw-r--r--activemodel/lib/active_model/validations.rb3
-rw-r--r--activemodel/lib/active_model/validations/absence.rb31
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb6
-rw-r--r--activemodel/lib/active_model/validations/length.rb16
-rw-r--r--activemodel/lib/active_model/validations/presence.rb4
-rw-r--r--activemodel/lib/active_model/validator.rb4
-rw-r--r--activemodel/test/cases/dirty_test.rb3
-rw-r--r--activemodel/test/cases/errors_test.rb5
-rw-r--r--activemodel/test/cases/helper.rb2
-rw-r--r--activemodel/test/cases/observer_array_test.rb220
-rw-r--r--activemodel/test/cases/observing_test.rb181
-rw-r--r--activemodel/test/cases/railtie_test.rb7
-rw-r--r--activemodel/test/cases/secure_password_test.rb12
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb5
-rw-r--r--[-rwxr-xr-x]activemodel/test/cases/serializers/xml_serialization_test.rb12
-rw-r--r--activemodel/test/cases/validations/absence_validation_test.rb67
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb39
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb36
-rw-r--r--activemodel/test/models/observers.rb27
-rw-r--r--activerecord/CHANGELOG.md228
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/README.rdoc11
-rw-r--r--activerecord/examples/performance.rb2
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/aggregations.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb26
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb4
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb32
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb1
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb20
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb3
-rw-r--r--activerecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb56
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb91
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb13
-rw-r--r--activerecord/lib/active_record/core.rb7
-rw-r--r--activerecord/lib/active_record/counter_cache.rb18
-rw-r--r--activerecord/lib/active_record/explain.rb13
-rw-r--r--activerecord/lib/active_record/fixtures.rb14
-rw-r--r--activerecord/lib/active_record/inheritance.rb74
-rw-r--r--activerecord/lib/active_record/integration.rb15
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb14
-rw-r--r--activerecord/lib/active_record/migration.rb175
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb123
-rw-r--r--activerecord/lib/active_record/migration/join_table.rb2
-rw-r--r--activerecord/lib/active_record/model_schema.rb7
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb2
-rw-r--r--activerecord/lib/active_record/observer.rb126
-rw-r--r--activerecord/lib/active_record/persistence.rb85
-rw-r--r--activerecord/lib/active_record/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/querying.rb7
-rw-r--r--activerecord/lib/active_record/railtie.rb31
-rw-r--r--activerecord/lib/active_record/railties/databases.rake6
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb27
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb58
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb124
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb146
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb31
-rw-r--r--activerecord/lib/active_record/schema.rb7
-rw-r--r--activerecord/lib/active_record/scoping/named.rb4
-rw-r--r--activerecord/lib/active_record/timestamp.rb4
-rw-r--r--activerecord/lib/active_record/transactions.rb22
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb65
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb20
-rw-r--r--activerecord/lib/rails/generators/active_record/observer/observer_generator.rb15
-rw-r--r--activerecord/lib/rails/generators/active_record/observer/templates/observer.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb41
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb3
-rw-r--r--activerecord/test/cases/adapters/postgresql/intrange_test.rb106
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb41
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb29
-rw-r--r--activerecord/test/cases/adapters/postgresql/sql_types_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/timestamp_test.rb18
-rw-r--r--activerecord/test/cases/aggregations_test.rb3
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb50
-rw-r--r--activerecord/test/cases/associations/eager_test.rb21
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb48
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb13
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb30
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb4
-rw-r--r--activerecord/test/cases/associations_test.rb8
-rw-r--r--activerecord/test/cases/autosave_association_test.rb8
-rw-r--r--activerecord/test/cases/base_test.rb44
-rw-r--r--activerecord/test/cases/calculations_test.rb49
-rw-r--r--activerecord/test/cases/column_test.rb20
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb11
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb12
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb8
-rw-r--r--activerecord/test/cases/date_time_test.rb6
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/deprecated_dynamic_methods_test.rb4
-rw-r--r--activerecord/test/cases/dirty_test.rb18
-rw-r--r--activerecord/test/cases/explain_test.rb12
-rw-r--r--activerecord/test/cases/finder_test.rb12
-rw-r--r--activerecord/test/cases/forbidden_attributes_protection_test.rb15
-rw-r--r--activerecord/test/cases/helper.rb5
-rw-r--r--activerecord/test/cases/inheritance_test.rb23
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb131
-rw-r--r--activerecord/test/cases/lifecycle_test.rb256
-rw-r--r--activerecord/test/cases/locking_test.rb2
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb9
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb18
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb43
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb8
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb208
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb48
-rw-r--r--activerecord/test/cases/migration/helper.rb6
-rw-r--r--activerecord/test/cases/migration/index_test.rb6
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb8
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb16
-rw-r--r--activerecord/test/cases/migration/rename_column_test.rb42
-rw-r--r--activerecord/test/cases/migration_test.rb4
-rw-r--r--activerecord/test/cases/migrator_test.rb14
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb108
-rw-r--r--activerecord/test/cases/persistence_test.rb85
-rw-r--r--activerecord/test/cases/primary_keys_test.rb6
-rw-r--r--activerecord/test/cases/query_cache_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb75
-rw-r--r--activerecord/test/cases/relation/where_test.rb6
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb45
-rw-r--r--activerecord/test/cases/relations_test.rb53
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb11
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb24
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb83
-rw-r--r--activerecord/test/cases/transaction_isolation_test.rb2
-rw-r--r--activerecord/test/cases/transactions_test.rb12
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb23
-rw-r--r--activerecord/test/migrations/10_urban/9_add_expressions.rb11
-rw-r--r--activerecord/test/models/author.rb1
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/company.rb1
-rw-r--r--activerecord/test/models/developer.rb15
-rw-r--r--activerecord/test/models/person.rb21
-rw-r--r--activerecord/test/models/reference.rb7
-rw-r--r--activerecord/test/models/reply.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb47
-rw-r--r--activerecord/test/schema/schema.rb2
-rw-r--r--activerecord/test/support/mysql.rb11
-rw-r--r--activesupport/CHANGELOG.md99
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/activesupport.gemspec1
-rw-r--r--activesupport/lib/active_support.rb3
-rw-r--r--activesupport/lib/active_support/basic_object.rb16
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb18
-rw-r--r--activesupport/lib/active_support/cache.rb3
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb1
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb1
-rw-r--r--activesupport/lib/active_support/callbacks.rb49
-rw-r--r--activesupport/lib/active_support/configurable.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date/infinite_comparable.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date/zones.rb22
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/exception.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb163
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/infinite_comparable.rb35
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/string/zones.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/thread.rb74
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/time/infinite_comparable.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb85
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/instance_delegator.rb6
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb2
-rw-r--r--activesupport/lib/active_support/duration.rb4
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb14
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb14
-rw-r--r--activesupport/lib/active_support/json/encoding.rb8
-rw-r--r--activesupport/lib/active_support/key_generator.rb8
-rw-r--r--activesupport/lib/active_support/logger.rb4
-rw-r--r--activesupport/lib/active_support/logger_silence.rb24
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb4
-rw-r--r--activesupport/lib/active_support/message_verifier.rb2
-rw-r--r--activesupport/lib/active_support/multibyte.rb2
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb7
-rw-r--r--activesupport/lib/active_support/proxy_object.rb13
-rw-r--r--activesupport/lib/active_support/queueing.rb105
-rw-r--r--activesupport/lib/active_support/railtie.rb30
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb2
-rw-r--r--activesupport/lib/active_support/test_case.rb46
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb18
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb5
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb2
-rw-r--r--activesupport/lib/active_support/testing/declarative.rb40
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb12
-rw-r--r--activesupport/lib/active_support/testing/pending.rb14
-rw-r--r--activesupport/lib/active_support/testing/tagged_logging.rb21
-rw-r--r--activesupport/lib/active_support/time.rb21
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb31
-rw-r--r--activesupport/test/abstract_unit.rb2
-rw-r--r--activesupport/test/caching_test.rb48
-rw-r--r--activesupport/test/callbacks_test.rb62
-rw-r--r--activesupport/test/configurable_test.rb2
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb15
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb17
-rw-r--r--activesupport/test/core_ext/duration_test.rb18
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/marshal_test.rb124
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb74
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb25
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb51
-rw-r--r--activesupport/test/core_ext/thread_test.rb77
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb119
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb138
-rw-r--r--activesupport/test/dependecies_test_helpers.rb27
-rw-r--r--activesupport/test/dependencies_test.rb43
-rw-r--r--activesupport/test/deprecation/basic_object_test.rb12
-rw-r--r--activesupport/test/deprecation/buffered_logger_test.rb22
-rw-r--r--activesupport/test/deprecation_test.rb7
-rw-r--r--activesupport/test/inflector_test.rb2
-rw-r--r--activesupport/test/json/encoding_test.rb22
-rw-r--r--activesupport/test/logger_test.rb10
-rw-r--r--activesupport/test/multibyte_chars_test.rb8
-rw-r--r--activesupport/test/queueing/synchronous_queue_test.rb27
-rw-r--r--activesupport/test/queueing/test_queue_test.rb146
-rw-r--r--activesupport/test/queueing/threaded_consumer_test.rb110
-rw-r--r--activesupport/test/spec_type_test.rb22
-rw-r--r--activesupport/test/string_inquirer_test.rb2
-rw-r--r--activesupport/test/test_case_test.rb6
-rw-r--r--activesupport/test/test_test.rb60
-rw-r--r--activesupport/test/time_zone_test.rb39
-rw-r--r--activesupport/test/ts_isolated.rb2
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb7
-rw-r--r--activesupport/test/xml_mini/libxmlsax_engine_test.rb7
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb7
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb7
-rw-r--r--guides/CHANGELOG.md2
-rw-r--r--guides/Rakefile6
-rw-r--r--guides/assets/images/customized_error_messages.pngbin2561 -> 0 bytes
-rw-r--r--guides/assets/images/error_messages.pngbin10964 -> 0 bytes
-rw-r--r--guides/assets/images/rails4_features.pngbin0 -> 132154 bytes
-rw-r--r--guides/assets/images/validation_error_messages.pngbin583 -> 0 bytes
-rw-r--r--guides/assets/stylesheets/main.css6
-rw-r--r--guides/code/getting_started/app/controllers/posts_controller.rb2
-rw-r--r--guides/code/getting_started/config/application.rb3
-rw-r--r--guides/rails_guides/generator.rb8
-rw-r--r--guides/rails_guides/kindle.rb119
-rw-r--r--guides/source/4_0_release_notes.md883
-rw-r--r--guides/source/_welcome.html.erb2
-rw-r--r--guides/source/action_controller_overview.md69
-rw-r--r--guides/source/action_mailer_basics.md109
-rw-r--r--guides/source/action_view_overview.md16
-rw-r--r--guides/source/active_model_basics.md94
-rw-r--r--guides/source/active_record_basics.md276
-rw-r--r--guides/source/active_record_callbacks.md362
-rw-r--r--guides/source/active_record_querying.md42
-rw-r--r--guides/source/active_record_validations.md1100
-rw-r--r--guides/source/active_record_validations_callbacks.md1368
-rw-r--r--guides/source/active_support_core_extensions.md81
-rw-r--r--guides/source/active_support_instrumentation.md34
-rw-r--r--guides/source/api_documentation_guidelines.md5
-rw-r--r--guides/source/asset_pipeline.md79
-rw-r--r--guides/source/association_basics.md146
-rw-r--r--guides/source/caching_with_rails.md114
-rw-r--r--guides/source/command_line.md20
-rw-r--r--guides/source/configuring.md35
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md23
-rw-r--r--guides/source/debugging_rails_applications.md20
-rw-r--r--guides/source/development_dependencies_install.md5
-rw-r--r--guides/source/documents.yaml14
-rw-r--r--guides/source/engines.md18
-rw-r--r--guides/source/form_helpers.md40
-rw-r--r--guides/source/generators.md18
-rw-r--r--guides/source/getting_started.md117
-rw-r--r--guides/source/i18n.md22
-rw-r--r--guides/source/index.html.erb4
-rw-r--r--guides/source/initialization.md8
-rw-r--r--guides/source/kindle/rails_guides.opf.erb2
-rw-r--r--guides/source/layouts_and_rendering.md41
-rw-r--r--guides/source/migrations.md819
-rw-r--r--guides/source/nested_model_forms.md4
-rw-r--r--guides/source/performance_testing.md20
-rw-r--r--guides/source/plugins.md15
-rw-r--r--guides/source/rails_application_templates.md6
-rw-r--r--guides/source/rails_on_rack.md18
-rw-r--r--guides/source/routing.md230
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md9
-rw-r--r--guides/source/security.md176
-rw-r--r--guides/source/testing.md18
-rw-r--r--guides/source/upgrading_ruby_on_rails.md44
-rw-r--r--guides/source/working_with_javascript_in_rails.md80
-rw-r--r--install.rb9
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG.md75
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/lib/rails.rb51
-rw-r--r--railties/lib/rails/application.rb20
-rw-r--r--railties/lib/rails/application/configuration.rb14
-rw-r--r--railties/lib/rails/application/finisher.rb10
-rw-r--r--railties/lib/rails/code_statistics.rb8
-rw-r--r--railties/lib/rails/commands.rb6
-rw-r--r--railties/lib/rails/commands/application.rb3
-rw-r--r--railties/lib/rails/commands/console.rb26
-rw-r--r--railties/lib/rails/commands/dbconsole.rb30
-rw-r--r--railties/lib/rails/commands/profiler.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb4
-rw-r--r--railties/lib/rails/commands/server.rb2
-rw-r--r--railties/lib/rails/engine.rb6
-rw-r--r--railties/lib/rails/engine/configuration.rb9
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/active_model.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb7
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb12
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb10
-rw-r--r--railties/lib/rails/generators/named_base.rb11
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README44
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb17
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory0
-rw-r--r--railties/lib/rails/generators/rails/controller/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/migration/USAGE18
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE14
-rw-r--r--railties/lib/rails/generators/rails/observer/USAGE12
-rw-r--r--railties/lib/rails/generators/rails/observer/observer_generator.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb15
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb9
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb37
-rw-r--r--railties/lib/rails/generators/test_case.rb4
-rw-r--r--railties/lib/rails/generators/test_unit/model/model_generator.rb12
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml10
-rw-r--r--railties/lib/rails/generators/test_unit/observer/observer_generator.rb13
-rw-r--r--railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb9
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb9
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb2
-rw-r--r--railties/lib/rails/info_controller.rb12
-rw-r--r--railties/lib/rails/paths.rb22
-rw-r--r--railties/lib/rails/railtie.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb2
-rw-r--r--railties/lib/rails/tasks/routes.rake4
-rw-r--r--railties/lib/rails/templates/layouts/application.html.erb4
-rw-r--r--railties/lib/rails/templates/rails/info/routes.html.erb2
-rw-r--r--railties/lib/rails/templates/rails/welcome/index.html.erb (renamed from railties/lib/rails/generators/rails/app/templates/public/index.html)24
-rw-r--r--railties/lib/rails/test_help.rb2
-rw-r--r--railties/lib/rails/welcome_controller.rb7
-rw-r--r--railties/test/abstract_unit.rb3
-rw-r--r--railties/test/application/assets_test.rb2
-rw-r--r--railties/test/application/configuration_test.rb103
-rw-r--r--railties/test/application/console_test.rb17
-rw-r--r--railties/test/application/initializers/frameworks_test.rb16
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb2
-rw-r--r--railties/test/application/paths_test.rb9
-rw-r--r--railties/test/application/queue_test.rb154
-rw-r--r--railties/test/application/rake/dbs_test.rb30
-rw-r--r--railties/test/application/rake/migrations_test.rb4
-rw-r--r--railties/test/application/rake/notes_test.rb8
-rw-r--r--railties/test/application/rake_test.rb33
-rw-r--r--railties/test/application/routing_test.rb107
-rw-r--r--railties/test/application/runner_test.rb18
-rw-r--r--railties/test/commands/console_test.rb29
-rw-r--r--railties/test/commands/dbconsole_test.rb20
-rw-r--r--railties/test/commands/server_test.rb16
-rw-r--r--railties/test/configuration/middleware_stack_proxy_test.rb3
-rw-r--r--railties/test/engine_test.rb2
-rw-r--r--railties/test/env_helpers.rb26
-rw-r--r--railties/test/generators/actions_test.rb31
-rw-r--r--railties/test/generators/app_generator_test.rb14
-rw-r--r--railties/test/generators/generated_attribute_test.rb11
-rw-r--r--railties/test/generators/migration_generator_test.rb107
-rw-r--r--railties/test/generators/model_generator_test.rb35
-rw-r--r--railties/test/generators/namespaced_generators_test.rb28
-rw-r--r--railties/test/generators/observer_generator_test.rb27
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb63
-rw-r--r--railties/test/generators/scaffold_generator_test.rb45
-rw-r--r--railties/test/generators/shared_generator_tests.rb2
-rw-r--r--railties/test/isolation/abstract_unit.rb2
-rw-r--r--railties/test/rails_info_controller_test.rb2
-rw-r--r--railties/test/railties/engine_test.rb2
686 files changed, 17023 insertions, 10506 deletions
diff --git a/.travis.yml b/.travis.yml
index 7e3d728872..257a6dc7e7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
script: 'ci/travis.rb'
before_install:
- - gem install bundler
+ - gem install bundler --pre
rvm:
- 1.9.3
- 2.0.0
@@ -22,7 +22,7 @@ notifications:
on_success: change
on_failure: always
rooms:
- - secure: "CGWvthGkBKNnTnk9YSmf9AXKoiRI33fCl5D3jU4nx3cOPu6kv2R9nMjt9EAo\nOuS4Q85qNSf4VNQ2cUPNiNYSWQ+XiTfivKvDUw/QW9r1FejYyeWarMsSBWA+\n0fADjF1M2dkDIVLgYPfwoXEv7l+j654F1KLKB69F0F/netwP9CQ="
+ - secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
bundler_args: --path vendor/bundle
matrix:
allow_failures:
diff --git a/Gemfile b/Gemfile
index 35bcb68fb2..94a0d15d16 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,10 +12,13 @@ gem 'jquery-rails', '~> 2.1.4', github: 'rails/jquery-rails'
gem 'turbolinks'
gem 'coffee-rails', github: 'rails/coffee-rails'
-gem 'journey', github: 'rails/journey', branch: 'master'
+gem 'thread_safe', '~> 0.1'
gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders', branch: 'master'
+# Needed for compiling the ActionDispatch::Journey parser
+gem 'racc', '>=1.4.6', require: false
+
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
gem 'uglifier', require: false
@@ -26,6 +29,7 @@ group :doc do
gem 'sdoc', github: 'voloko/sdoc'
gem 'redcarpet', '~> 2.2.2', platforms: :ruby
gem 'w3c_validators'
+ gem 'kindlerb'
end
# AS
diff --git a/README.rdoc b/README.rdoc
index 87ec64e3b5..91a5f27add 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -56,10 +56,10 @@ can read more about Action Pack in its {README}[link:/rails/rails/blob/master/ac
5. Follow the guidelines to start developing your application. You may find the following resources handy:
* The README file created within your application.
-* The {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html].
-* The {Ruby on Rails Tutorial}[http://railstutorial.org/book].
-* The {Ruby on Rails Guides}[http://guides.rubyonrails.org].
-* The {API Documentation}[http://api.rubyonrails.org].
+* {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html].
+* {Ruby on Rails Tutorial}[http://ruby.railstutorial.org/ruby-on-rails-tutorial-book].
+* {Ruby on Rails Guides}[http://guides.rubyonrails.org].
+* {The API Documentation}[http://api.rubyonrails.org].
== Contributing
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index af1def223a..9af79f73e2 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -164,6 +164,9 @@ Today, do this stuff in this order:
* Update RAILS_VERSION to remove the rc
* Build and test the gem
* Release the gems
+* If releasing a new stable version:
+ - Trigger stable docs generation (see below)
+ - Update the version in the home page
* Email security lists
* Email general announcement lists
diff --git a/Rakefile b/Rakefile
index 1caa69cf92..490627d22c 100644
--- a/Rakefile
+++ b/Rakefile
@@ -40,10 +40,10 @@ task :install => :gem do
version = File.read("RAILS_VERSION").strip
(PROJECTS - ["railties"]).each do |project|
puts "INSTALLING #{project}"
- system("gem install #{project}/pkg/#{project}-#{version}.gem --no-ri --no-rdoc")
+ system("gem install #{project}/pkg/#{project}-#{version}.gem --local --no-ri --no-rdoc")
end
- system("gem install railties/pkg/railties-#{version}.gem --no-ri --no-rdoc")
- system("gem install pkg/rails-#{version}.gem --no-ri --no-rdoc")
+ system("gem install railties/pkg/railties-#{version}.gem --local --no-ri --no-rdoc")
+ system("gem install pkg/rails-#{version}.gem --local --no-ri --no-rdoc")
end
desc "Generate documentation for the Rails framework"
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 28a5c0ab71..9feca324a3 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,34 +1,35 @@
## Rails 4.0.0 (unreleased) ##
-* Do not render views when mail() isn't called.
- Fix #7761
+* Explicit multipart messages no longer set the order of the MIME parts.
+ *Nate Berkopec*
- *Yves Senn*
+* Do not render views when mail() isn't called.
+ Fix #7761
-* Allow delivery method options to be set per mail instance *Aditya Sanghi*
+ *Yves Senn*
- If your smtp delivery settings are dynamic,
- you can now override settings per mail instance for e.g.
+* Allow delivery method options to be set per mail instance *Aditya Sanghi*
- def my_mailer(user,company)
- mail to: user.email, subject: "Welcome!",
- delivery_method_options: {user_name: company.smtp_user,
- password: company.smtp_password}
- end
+ If your smtp delivery settings are dynamic,
+ you can now override settings per mail instance for e.g.
- This will ensure that your default SMTP settings will be overridden
- by the company specific ones. You only have to override the settings
- that are dynamic and leave the static setting in your environment
- configuration file (e.g. config/environments/production.rb)
+ def my_mailer(user,company)
+ mail to: user.email, subject: "Welcome!",
+ delivery_method_options: { user_name: company.smtp_user,
+ password: company.smtp_password }
+ end
-* Allow to set default Action Mailer options via `config.action_mailer.default_options=` *Robert Pankowecki*
+ This will ensure that your default SMTP settings will be overridden
+ by the company specific ones. You only have to override the settings
+ that are dynamic and leave the static setting in your environment
+ configuration file (e.g. config/environments/production.rb)
-* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu*
+* Allow to set default Action Mailer options via `config.action_mailer.default_options=` *Robert Pankowecki*
-* Asynchronously send messages via the Rails Queue *Brian Cardarella*
+* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu*
-* Allow callbacks to be defined in mailers similar to `ActionController::Base`. You can configure default
- settings, headers, attachments, delivery settings or change delivery using
- `before_filter`, `after_filter` etc. *Justin S. Leitgeb*
+* Allow callbacks to be defined in mailers similar to `ActionController::Base`. You can configure default
+ settings, headers, attachments, delivery settings or change delivery using
+ `before_filter`, `after_filter` etc. *Justin S. Leitgeb*
Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 810daf856c..5c668d9624 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2012 David Heinemeier Hansson
+Copyright (c) 2004-2013 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index e9f979f34b..67ec0d1097 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -21,5 +21,5 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
- s.add_dependency 'mail', '~> 2.5.2'
+ s.add_dependency 'mail', '~> 2.5.3'
end
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index a9642dc695..c45124be80 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -44,5 +44,4 @@ module ActionMailer
autoload :MailHelper
autoload :TestCase
autoload :TestHelper
- autoload :QueuedMessage
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 6a9828fde7..9ba606c045 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,10 +1,8 @@
require 'mail'
-require 'action_mailer/queued_message'
require 'action_mailer/collector'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
-require 'active_support/queueing'
require 'action_mailer/log_subscriber'
module ActionMailer
@@ -20,8 +18,6 @@ module ActionMailer
# used to generate an email message. In these methods, you can setup variables to be used in
# the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
#
- # Examples:
- #
# class Notifier < ActionMailer::Base
# default from: 'no-reply@example.com',
# return_path: 'system@example.com'
@@ -286,12 +282,12 @@ module ActionMailer
#
# = Callbacks
#
- # You can specify callbacks using before_filter and after_filter for configuring your messages.
+ # You can specify callbacks using before_action and after_action for configuring your messages.
# This may be useful, for example, when you want to add default inline attachments for all
# messages sent out by a certain mailer class:
#
# class Notifier < ActionMailer::Base
- # before_filter :add_inline_attachment!
+ # before_action :add_inline_attachment!
#
# def welcome
# mail
@@ -308,15 +304,15 @@ module ActionMailer
# can define and configure callbacks in the same manner that you would use callbacks in
# classes that inherit from ActionController::Base.
#
- # Note that unless you have a specific reason to do so, you should prefer using before_filter
- # rather than after_filter in your ActionMailer classes so that headers are parsed properly.
+ # Note that unless you have a specific reason to do so, you should prefer using before_action
+ # rather than after_action in your ActionMailer classes so that headers are parsed properly.
#
# = Configuration options
#
# These options are specified on the class level, like
# <tt>ActionMailer::Base.raise_delivery_errors = true</tt>
#
- # * <tt>default</tt> - You can pass this in at a class level as well as within the class itself as
+ # * <tt>default_options</tt> - You can pass this in at a class level as well as within the class itself as
# per the above section.
#
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
@@ -363,8 +359,6 @@ module ActionMailer
#
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
- #
- # * <tt>queue</> - The queue that will be used to deliver the mail. The queue should expect a job that responds to <tt>run</tt>.
class Base < AbstractController::Base
include DeliveryMethods
abstract!
@@ -391,9 +385,6 @@ module ActionMailer
parts_order: [ "text/plain", "text/enriched", "text/html" ]
}.freeze
- class_attribute :queue
- self.queue = ActiveSupport::SynchronousQueue.new
-
class << self
# Register one or more Observers which will be notified when mail is delivered.
def register_observers(*observers)
@@ -485,8 +476,8 @@ module ActionMailer
end
def method_missing(method_name, *args)
- if action_methods.include?(method_name.to_s)
- QueuedMessage.new(queue, self, method_name, *args)
+ if respond_to?(method_name)
+ new(method_name, *args).message
else
super
end
@@ -501,6 +492,7 @@ module ActionMailer
# method, for instance).
def initialize(method_name=nil, *args)
super()
+ @_mail_was_called = false
@_message = Mail.new
process(method_name, *args) if method_name
end
@@ -508,10 +500,8 @@ module ActionMailer
def process(*args) #:nodoc:
lookup_context.skip_default_locale!
- generated_mail = super
- unless generated_mail
- @_message = NullMail.new
- end
+ super
+ @_message = NullMail.new unless @_mail_was_called
end
class NullMail #:nodoc:
@@ -540,7 +530,7 @@ module ActionMailer
# The resulting Mail::Message will have the following in its header:
#
# X-Special-Domain-Specific-Header: SecretValue
- def headers(args=nil)
+ def headers(args = nil)
if args
@_message.headers(args)
else
@@ -670,12 +660,12 @@ module ActionMailer
# format.html
# end
#
- def mail(headers={}, &block)
+ def mail(headers = {}, &block)
+ @_mail_was_called = true
m = @_message
- # At the beginning, do not consider class default for parts order neither content_type
+ # At the beginning, do not consider class default for content_type
content_type = headers[:content_type]
- parts_order = headers[:parts_order]
# Call all the procs (if any)
class_default = self.class.default
@@ -698,7 +688,7 @@ module ActionMailer
assignable.each { |k, v| m[k] = v }
# Render the templates and blocks
- responses, explicit_order = collect_responses_and_parts_order(headers, &block)
+ responses = collect_responses(headers, &block)
create_parts_from_responses(m, responses)
# Setup content type, reapply charset and handle parts order
@@ -706,8 +696,7 @@ module ActionMailer
m.charset = charset
if m.multipart?
- parts_order ||= explicit_order || headers[:parts_order]
- m.body.set_sort_order(parts_order)
+ m.body.set_sort_order(headers[:parts_order])
m.body.sort_parts!
end
@@ -742,14 +731,13 @@ module ActionMailer
I18n.t(:subject, scope: [mailer_scope, action_name], default: action_name.humanize)
end
- def collect_responses_and_parts_order(headers) #:nodoc:
- responses, parts_order = [], nil
+ def collect_responses(headers) #:nodoc:
+ responses = []
if block_given?
collector = ActionMailer::Collector.new(lookup_context) { render(action_name) }
yield(collector)
- parts_order = collector.responses.map { |r| r[:content_type] }
- responses = collector.responses
+ responses = collector.responses
elsif headers[:body]
responses << {
body: headers.delete(:body),
@@ -769,7 +757,7 @@ module ActionMailer
end
end
- [responses, parts_order]
+ responses
end
def each_template(paths, name, &block) #:nodoc:
diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb
index cbb2778fae..e8883a8235 100644
--- a/actionmailer/lib/action_mailer/collector.rb
+++ b/actionmailer/lib/action_mailer/collector.rb
@@ -20,7 +20,7 @@ module ActionMailer
end
alias :all :any
- def custom(mime, options={})
+ def custom(mime, options = {})
options.reverse_merge!(content_type: mime.to_s)
@context.formats = [mime.to_sym]
options[:body] = block_given? ? yield : @default_render.call
diff --git a/actionmailer/lib/action_mailer/queued_message.rb b/actionmailer/lib/action_mailer/queued_message.rb
deleted file mode 100644
index 8d200617c4..0000000000
--- a/actionmailer/lib/action_mailer/queued_message.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'delegate'
-
-module ActionMailer
- class QueuedMessage < ::Delegator
- attr_reader :queue
-
- def initialize(queue, mailer_class, method_name, *args)
- @queue = queue
- @job = DeliveryJob.new(mailer_class, method_name, args)
- end
-
- def __getobj__
- @job.message
- end
-
- # Queues the message for delivery.
- def deliver
- tap { @queue.push @job }
- end
-
- class DeliveryJob
- def initialize(mailer_class, method_name, args)
- @mailer_class = mailer_class
- @method_name = method_name
- @args = args
- end
-
- def message
- @message ||= @mailer_class.send(:new, @method_name, *@args).message
- end
-
- def run
- message.deliver
- end
- end
- end
-end
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 59dc26841f..7677ff3a7c 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -19,8 +19,6 @@ module ActionMailer
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
- options.queue ||= app.queue
-
# make sure readers methods get compiled
options.asset_host ||= app.config.asset_host
options.relative_url_root ||= app.config.relative_url_root
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index 80f323873d..207f949fe2 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -10,13 +10,6 @@ module ActionMailer
end
class TestCase < ActiveSupport::TestCase
-
- # Use AM::TestCase for the base class when describing a mailer
- register_spec_type(self) do |desc|
- Class === desc && desc < ActionMailer::Base
- end
- register_spec_type(/Mailer( ?Test)?\z/i, self)
-
module Behavior
extend ActiveSupport::Concern
diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE
index 7470289fd3..323bb8a87f 100644
--- a/actionmailer/lib/rails/generators/mailer/USAGE
+++ b/actionmailer/lib/rails/generators/mailer/USAGE
@@ -10,9 +10,8 @@ Example:
========
rails generate mailer Notifications signup forgot_password invoice
- creates a Notifications mailer class, views, test, and fixtures:
+ creates a Notifications mailer class, views, and test:
Mailer: app/mailers/notifications.rb
- Views: app/views/notifications/signup.erb [...]
+ Views: app/views/notifications/signup.text.erb [...]
Test: test/mailers/notifications_test.rb
- Fixtures: test/fixtures/notifications/signup [...]
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 4b38d4bd31..15729bafba 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -8,10 +8,9 @@ silence_warnings do
Encoding.default_external = "UTF-8"
end
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'action_mailer'
require 'action_mailer/test_case'
-require 'active_support/queueing'
silence_warnings do
# These external dependencies have warnings :/
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index b07b352082..b06c465380 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -3,13 +3,11 @@ require 'abstract_unit'
require 'set'
require 'action_dispatch'
-require 'active_support/queueing'
require 'active_support/time'
require 'mailers/base_mailer'
require 'mailers/proc_mailer'
require 'mailers/asset_mailer'
-require 'mailers/async_mailer'
class BaseTest < ActiveSupport::TestCase
def teardown
@@ -322,19 +320,6 @@ class BaseTest < ActiveSupport::TestCase
assert_not_nil(mail.content_type_parameters[:boundary])
end
- test "explicit multipart does not sort order" do
- order = ["text/html", "text/plain"]
- with_default BaseMailer, parts_order: order do
- email = BaseMailer.explicit_multipart
- assert_equal("text/plain", email.parts[0].mime_type)
- assert_equal("text/html", email.parts[1].mime_type)
-
- email = BaseMailer.explicit_multipart(parts_order: order.reverse)
- assert_equal("text/plain", email.parts[0].mime_type)
- assert_equal("text/html", email.parts[1].mime_type)
- end
- end
-
test "explicit multipart with attachments creates nested parts" do
email = BaseMailer.explicit_multipart(attachments: true)
assert_equal("application/pdf", email.parts[0].mime_type)
@@ -349,10 +334,10 @@ class BaseTest < ActiveSupport::TestCase
email = BaseMailer.explicit_multipart_templates
assert_equal(2, email.parts.size)
assert_equal("multipart/alternative", email.mime_type)
- assert_equal("text/html", email.parts[0].mime_type)
- assert_equal("HTML Explicit Multipart Templates", email.parts[0].body.encoded)
- assert_equal("text/plain", email.parts[1].mime_type)
- assert_equal("TEXT Explicit Multipart Templates", email.parts[1].body.encoded)
+ assert_equal("text/plain", email.parts[0].mime_type)
+ assert_equal("TEXT Explicit Multipart Templates", email.parts[0].body.encoded)
+ assert_equal("text/html", email.parts[1].mime_type)
+ assert_equal("HTML Explicit Multipart Templates", email.parts[1].body.encoded)
end
test "explicit multipart with format.any" do
@@ -387,10 +372,23 @@ class BaseTest < ActiveSupport::TestCase
email = BaseMailer.explicit_multipart_with_one_template
assert_equal(2, email.parts.size)
assert_equal("multipart/alternative", email.mime_type)
- assert_equal("text/html", email.parts[0].mime_type)
- assert_equal("[:html]", email.parts[0].body.encoded)
- assert_equal("text/plain", email.parts[1].mime_type)
- assert_equal("[:text]", email.parts[1].body.encoded)
+ assert_equal("text/plain", email.parts[0].mime_type)
+ assert_equal("[:text]", email.parts[0].body.encoded)
+ assert_equal("text/html", email.parts[1].mime_type)
+ assert_equal("[:html]", email.parts[1].body.encoded)
+ end
+
+ test "explicit multipart with sort order" do
+ order = ["text/html", "text/plain"]
+ with_default BaseMailer, parts_order: order do
+ email = BaseMailer.explicit_multipart
+ assert_equal("text/html", email.parts[0].mime_type)
+ assert_equal("text/plain", email.parts[1].mime_type)
+
+ email = BaseMailer.explicit_multipart(parts_order: order.reverse)
+ assert_equal("text/plain", email.parts[0].mime_type)
+ assert_equal("text/html", email.parts[1].mime_type)
+ end
end
# Class level API with method missing
@@ -422,17 +420,6 @@ class BaseTest < ActiveSupport::TestCase
assert_equal(1, BaseMailer.deliveries.length)
end
- test "delivering message asynchronously" do
- AsyncMailer.delivery_method = :test
- AsyncMailer.deliveries.clear
-
- AsyncMailer.welcome.deliver
- assert_equal 0, AsyncMailer.deliveries.length
-
- AsyncMailer.queue.drain
- assert_equal 1, AsyncMailer.deliveries.length
- end
-
test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do
mail = Mail::Message.new
mail.expects(:do_delivery).once
@@ -505,6 +492,12 @@ class BaseTest < ActiveSupport::TestCase
mail.deliver
end
+ test 'the return value of mailer methods is not relevant' do
+ mail = BaseMailer.with_nil_as_return_value
+ assert_equal('Welcome', mail.body.to_s.strip)
+ mail.deliver
+ end
+
# Before and After hooks
class MyObserver
@@ -584,9 +577,9 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("Thanks for signing up this afternoon", mail.subject)
end
- test "modifying the mail message with a before_filter" do
- class BeforeFilterMailer < ActionMailer::Base
- before_filter :add_special_header!
+ test "modifying the mail message with a before_action" do
+ class BeforeActionMailer < ActionMailer::Base
+ before_action :add_special_header!
def welcome ; mail ; end
@@ -596,12 +589,12 @@ class BaseTest < ActiveSupport::TestCase
end
end
- assert_equal('Wow, so special', BeforeFilterMailer.welcome['X-Special-Header'].to_s)
+ assert_equal('Wow, so special', BeforeActionMailer.welcome['X-Special-Header'].to_s)
end
- test "modifying the mail message with an after_filter" do
- class AfterFilterMailer < ActionMailer::Base
- after_filter :add_special_header!
+ test "modifying the mail message with an after_action" do
+ class AfterActionMailer < ActionMailer::Base
+ after_action :add_special_header!
def welcome ; mail ; end
@@ -611,12 +604,12 @@ class BaseTest < ActiveSupport::TestCase
end
end
- assert_equal('Testing', AfterFilterMailer.welcome['X-Special-Header'].to_s)
+ assert_equal('Testing', AfterActionMailer.welcome['X-Special-Header'].to_s)
end
- test "adding an inline attachment using a before_filter" do
+ test "adding an inline attachment using a before_action" do
class DefaultInlineAttachmentMailer < ActionMailer::Base
- before_filter :add_inline_attachment!
+ before_action :add_inline_attachment!
def welcome ; mail ; end
diff --git a/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb b/actionmailer/test/fixtures/base_test/after_action_mailer/welcome.html.erb
index e69de29bb2..e69de29bb2 100644
--- a/actionmailer/test/fixtures/base_test/after_filter_mailer/welcome.html.erb
+++ b/actionmailer/test/fixtures/base_test/after_action_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_action_mailer/welcome.html.erb
index e69de29bb2..e69de29bb2 100644
--- a/actionmailer/test/fixtures/base_test/before_filter_mailer/welcome.html.erb
+++ b/actionmailer/test/fixtures/base_test/before_action_mailer/welcome.html.erb
diff --git a/actionmailer/test/mailers/async_mailer.rb b/actionmailer/test/mailers/async_mailer.rb
deleted file mode 100644
index c21a464f38..0000000000
--- a/actionmailer/test/mailers/async_mailer.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class AsyncMailer < BaseMailer
- self.queue = ActiveSupport::TestQueue.new
-end
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
index 52342bc59e..8fca6177bd 100644
--- a/actionmailer/test/mailers/base_mailer.rb
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -118,4 +118,9 @@ class BaseMailer < ActionMailer::Base
def without_mail_call
end
+
+ def with_nil_as_return_value(hash = {})
+ mail(:template_name => "welcome")
+ nil
+ end
end
diff --git a/actionmailer/test/spec_type_test.rb b/actionmailer/test/spec_type_test.rb
deleted file mode 100644
index 90db59c2d2..0000000000
--- a/actionmailer/test/spec_type_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'abstract_unit'
-
-class NotificationMailer < ActionMailer::Base; end
-class Notifications < ActionMailer::Base; end
-
-class SpecTypeTest < ActiveSupport::TestCase
- def assert_mailer actual
- assert_equal ActionMailer::TestCase, actual
- end
-
- def refute_mailer actual
- refute_equal ActionMailer::TestCase, actual
- end
-
- def test_spec_type_resolves_for_class_constants
- assert_mailer MiniTest::Spec.spec_type(NotificationMailer)
- assert_mailer MiniTest::Spec.spec_type(Notifications)
- end
-
- def test_spec_type_resolves_for_matching_strings
- assert_mailer MiniTest::Spec.spec_type("WidgetMailer")
- assert_mailer MiniTest::Spec.spec_type("WidgetMailerTest")
- assert_mailer MiniTest::Spec.spec_type("Widget Mailer Test")
- # And is not case sensitive
- assert_mailer MiniTest::Spec.spec_type("widgetmailer")
- assert_mailer MiniTest::Spec.spec_type("widgetmailertest")
- assert_mailer MiniTest::Spec.spec_type("widget mailer test")
- end
-
- def test_spec_type_wont_match_non_space_characters
- refute_mailer MiniTest::Spec.spec_type("Widget Mailer\tTest")
- refute_mailer MiniTest::Spec.spec_type("Widget Mailer\rTest")
- refute_mailer MiniTest::Spec.spec_type("Widget Mailer\nTest")
- refute_mailer MiniTest::Spec.spec_type("Widget Mailer\fTest")
- refute_mailer MiniTest::Spec.spec_type("Widget MailerXTest")
- end
-end
diff --git a/actionmailer/test/test_test.rb b/actionmailer/test/test_test.rb
index 139eb53359..86fd37bea6 100644
--- a/actionmailer/test/test_test.rb
+++ b/actionmailer/test/test_test.rb
@@ -26,147 +26,3 @@ class CrazyStringNameMailerTest < ActionMailer::TestCase
assert_equal TestTestMailer, self.class.mailer_class
end
end
-
-describe TestTestMailer do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe TestTestMailer, :action do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe TestTestMailer do
- describe "nested" do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe TestTestMailer, :action do
- describe "nested" do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe "TestTestMailer" do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe "TestTestMailerTest" do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe "TestTestMailer" do
- describe "nested" do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe "TestTestMailerTest" do
- describe "nested" do
- it "gets the mailer from the test name" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe "AnotherCrazySymbolNameMailerTest" do
- tests :test_test_mailer
-
- it "gets the mailer after setting it with a symbol" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe "AnotherCrazyStringNameMailerTest" do
- tests 'test_test_mailer'
-
- it "gets the mailer after setting it with a string" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe "Another Crazy Name Mailer Test" do
- tests TestTestMailer
-
- it "gets the mailer after setting it manually" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe "Another Crazy Symbol Name Mailer Test" do
- tests :test_test_mailer
-
- it "gets the mailer after setting it with a symbol" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe "Another Crazy String Name Mailer Test" do
- tests 'test_test_mailer'
-
- it "gets the mailer after setting it with a string" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
-
-describe "AnotherCrazySymbolNameMailerTest" do
- tests :test_test_mailer
-
- describe "nested" do
- it "gets the mailer after setting it with a symbol" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe "AnotherCrazyStringNameMailerTest" do
- tests 'test_test_mailer'
-
- describe "nested" do
- it "gets the mailer after setting it with a string" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe "Another Crazy Name Mailer Test" do
- tests TestTestMailer
-
- describe "nested" do
- it "gets the mailer after setting it manually" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe "Another Crazy Symbol Name Mailer Test" do
- tests :test_test_mailer
-
- describe "nested" do
- it "gets the mailer after setting it with a symbol" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
- end
-end
-
-describe "Another Crazy String Name Mailer Test" do
- tests 'test_test_mailer'
-
- it "gets the mailer after setting it with a string" do
- assert_equal TestTestMailer, self.class.mailer_class
- end
-end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 1ebc75ed2f..334cf9e1fc 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,34 +1,229 @@
## Rails 4.0.0 (unreleased) ##
+* Return the last valid, non-private IP address from the X-Forwarded-For,
+ Client-IP and Remote-Addr headers, in that order. Document the rationale
+ for that decision, and describe the options that can be passed to the
+ RemoteIp middleware to change it.
+ Fix #7979
+
+ *André Arko*, *Steve Klabnik*, *Alexey Gaziev*
+
+* Do not append second slash to `root_url` when using `trailing_slash: true`
+ Fix #8700
+
+ Example:
+ # before
+ root_url # => http://test.host//
+
+ # after
+ root_url # => http://test.host/
+
+ *Yves Senn*
+
+* Allow to toggle dumps on error pages.
+
+ *Gosha Arinich*
+
+* Fix a bug in `content_tag_for` that prevents it from working without a block.
+
+ *Jasl*
+
+* Change the stylesheet of exception pages for development mode.
+ Additionally display also the line of code and fragment that raised
+ the exception in all exceptions pages.
+
+ *Guillermo Iguaran + Jorge Cuadrado*
+
+* Do not append `charset=` parameter when `head` is called with a
+ `:content_type` option.
+ Fix #8661.
+
+ *Yves Senn*
+
+* Added `Mime::NullType` class. This allows to use html?, xml?, json?..etc when
+ the `format` of `request` is unknown, without raise an exception.
+
+ *Angelo Capilleri*
+
+* Integrate the Journey gem into Action Dispatch so that the global namespace
+ is not polluted with names that may be used as models.
+
+ *Andrew White*
+
+* Extract support for email address obfuscation via `:encode`, `:replace_at`, and `replace_dot`
+ options from the `mail_to` helper into the `actionview-encoded_mail_to` gem.
+
+ *Nick Reed + DHH*
+
+* Handle `:protocol` option in `stylesheet_link_tag` and `javascript_include_tag`
+
+ *Vasiliy Ermolovich*
+
+* Clear url helper methods when routes are reloaded. *Andrew White*
+
+* Fix a bug in `ActionDispatch::Request#raw_post` that caused `env['rack.input']`
+ to be read but not rewound.
+
+ *Matt Venables*
+
+* Prevent raising EOFError on multipart GET request (IE issue). *Adam Stankiewicz*
+
+* Rename all action callbacks from *_filter to *_action to avoid the misconception that these
+ callbacks are only suited for transforming or halting the response. With the new style,
+ it's more inviting to use them as they were intended, like setting shared ivars for views.
+
+ Example:
+
+ class PeopleController < ActionController::Base
+ before_action :set_person, except: [:index, :new, :create]
+ before_action :ensure_permission, only: [:edit, :update]
+
+ ...
+
+ private
+ def set_person
+ @person = current_account.people.find(params[:id])
+ end
+
+ def ensure_permission
+ current_person.can_change?(@person)
+ end
+ end
+
+ The old *_filter methods still work with no deprecation notice.
+
+ *DHH*
+
+* Add `cache_if` and `cache_unless` for conditional fragment caching:
+
+ Example:
+
+ <%= cache_if condition, project do %>
+ <b>All the topics on this project</b>
+ <%= render project.topics %>
+ <% end %>
+
+ # and
+
+ <%= cache_unless condition, project do %>
+ <b>All the topics on this project</b>
+ <%= render project.topics %>
+ <% end %>
+
+ *Stephen Ausman + Fabrizio Regini + Angelo Capilleri*
+
+* Add filter capability to ActionController logs for redirect locations:
+
+ config.filter_redirect << 'http://please.hide.it/'
+
+ *Fabrizio Regini*
+
+* Fixed a bug that ignores constraints on a glob route. This was caused because the constraint
+ regular expression is overwritten when the `routes.rb` file is processed. Fixes #7924
+
+ *Maura Fitzgerald*
+
+* More descriptive error messages when calling `render :partial` with
+ an invalid `:layout` argument.
+ #8376
+
+ render partial: 'partial', layout: true
+
+ # results in ActionView::MissingTemplate: Missing partial /true
+
+ *Yves Senn*
+
+* Sweepers was extracted from Action Controller as `rails-observers` gem.
+
+ *Rafael Mendonça França*
+
+* Add option flag to `CacheHelper#cache` to manually bypass automatic template digests:
+
+ <% cache project, skip_digest: true do %>
+ ...
+ <% end %>
+
+ *Drew Ulmer*
+
+* Do not sort Hash options in `grouped_options_for_select`. *Sergey Kojin*
+
+* Accept symbols as `send_data :disposition` value *Elia Schito*
+
+* Add i18n scope to `distance_of_time_in_words`. *Steve Klabnik*
+
+* `assert_template`:
+ - is no more passing with empty string.
+ - is now validating option keys. It accepts: `:layout`, `:partial`, `:locals` and `:count`.
+
+ *Roberto Soares*
+
+* Allow setting a symbol as path in scope on routes. This is now allowed:
+
+ scope :api do
+ resources :users
+ end
+
+ It is also possible to pass multiple symbols to scope to shorten multiple nested scopes:
+
+ scope :api do
+ scope :v1 do
+ resources :users
+ end
+ end
+
+ can be rewritten as:
+
+ scope :api, :v1 do
+ resources :users
+ end
+
+ *Guillermo Iguaran + Amparo Luna*
+
+* Fix error when using a non-hash query argument named "params" in `url_for`.
+
+ Before:
+
+ url_for(params: "") # => undefined method `reject!' for "":String
+
+ After:
+
+ url_for(params: "") # => http://www.example.com?params=
+
+ *tumayun + Carlos Antonio da Silva*
+
+* Render every partial with a new `ActionView::PartialRenderer`. This resolves
+ issues when rendering nested partials.
+ Fix #8197.
+
+ *Yves Senn*
+
* Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list
of mime types where template text is not html escaped by default. It prevents `Jack & Joe`
from rendering as `Jack &amp; Joe` for the whitelisted mime types. The default whitelist
- contains text/plain. Fix #7976
+ contains `text/plain`.
+ Fix #7976.
*Joost Baaij*
-* Fix input name when `:multiple => true` and `:index` are set.
+* Fix input name when `multiple: true` and `:index` are set.
Before:
- check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
#=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" />
After:
- check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
+ check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1)
#=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
- Fix #8108
+ Fix #8108.
*Daniel Fox, Grant Hutchins & Trace Wax*
-* Clear url helpers when reloading routes.
-
- *Santiago Pastorino*
-
* `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's
- returned value if any. Fix #8086
+ returned value if any.
+ Fix #8086.
*Nikita Afanasenko*
@@ -37,21 +232,21 @@
*Pavel Nikitin*
-* Only non-js/css under app/assets path will be included in default config.assets.precompile.
+* Only non-js/css under `app/assets` path will be included in default `config.assets.precompile`.
*Josh Peek*
-* Remove support for the RAILS_ASSET_ID environment configuration
+* Remove support for the `RAILS_ASSET_ID` environment configuration
(no longer needed now that we have the asset pipeline).
*Josh Peek*
-* Remove old asset_path configuration (no longer needed now that we have the asset pipeline).
+* Remove old `asset_path` configuration (no longer needed now that we have the asset pipeline).
*Josh Peek*
* `assert_template` can be used to assert on the same template with different locals
- Fix #3675
+ Fix #3675.
*Yves Senn*
@@ -59,10 +254,10 @@
*Josh Peek*
-* Accept :remote as symbolic option for `link_to` helper. *Riley Lynch*
+* Accept `:remote` as symbolic option for `link_to` helper. *Riley Lynch*
* Warn when the `:locals` option is passed to `assert_template` outside of a view test case
- Fix #3415
+ Fix #3415.
*Yves Senn*
@@ -82,24 +277,24 @@
*Francesco Rodriguez*
-* Failsafe exception returns text/plain. *Steve Klabnik*
+* Failsafe exception returns `text/plain`. *Steve Klabnik*
* Remove `rack-cache` dependency from Action Pack and declare it on Gemfile
*Guillermo Iguaran*
-* Rename internal variables on ActionController::TemplateAssertions to prevent
- naming collisions. @partials, @templates and @layouts are now prefixed with an underscore.
- Fix #7459
+* Rename internal variables on `ActionController::TemplateAssertions` to prevent
+ naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore.
+ Fix #7459.
*Yves Senn*
-* `resource` and `resources` don't modify the passed options hash
- Fix #7777
+* `resource` and `resources` don't modify the passed options hash.
+ Fix #7777.
*Yves Senn*
-* Precompiled assets include aliases from foo.js to foo/index.js and vice versa.
+* Precompiled assets include aliases from `foo.js` to `foo/index.js` and vice versa.
# Precompiles phone-<digest>.css and aliases phone/index.css to phone.css.
config.assets.precompile = [ 'phone.css' ]
@@ -137,8 +332,8 @@
*Nihad Abbasov*
-* Deprecate Mime::Type#verify_request? and Mime::Type.browser_generated_types,
- since they are no longer used inside of Rails, they will be removed in Rails 4.1
+* Deprecate `Mime::Type#verify_request?` and `Mime::Type.browser_generated_types`,
+ since they are no longer used inside of Rails, they will be removed in Rails 4.1.
*Michael Grosser*
@@ -154,11 +349,12 @@
* Remove Integration between `attr_accessible`/`attr_protected` and
`ActionController::ParamsWrapper`. ParamWrapper now wraps all the parameters returned
- by the class method attribute_names
+ by the class method `attribute_names`.
*Guillermo Iguaran*
-* Fix #7646, the log now displays the correct status code when an exception is raised.
+* Log now displays the correct status code when an exception is raised.
+ Fix #7646.
*Yves Senn*
@@ -192,15 +388,15 @@
New applications are generated with:
- protect_from_forgery :with => :exception
+ protect_from_forgery with: :exception
*Sergey Nartimov*
-* Add .ruby template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran*
+* Add `.ruby` template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran*
* Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`:
- excerpt('This is a very beautiful morning', 'very', :separator => ' ', :radius => 1)
+ excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
# => ...a very beautiful...
*Guirec Corbel*
@@ -217,7 +413,7 @@
end
end
-* Add automatic template digests to all `CacheHelper#cache` calls (originally spiked in the cache_digests plugin) *DHH*
+* Add automatic template digests to all `CacheHelper#cache` calls (originally spiked in the `cache_digests` plugin) *DHH*
* When building a URL fails, add missing keys provided by Journey. Failed URL
generation now returns a 500 status instead of a 404.
@@ -311,13 +507,13 @@
*Egor Homakov*
-* Allow data attributes to be set as a first-level option for form_for, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH*
+* Allow data attributes to be set as a first-level option for `form_for`, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH*
* Deprecate `button_to_function` and `link_to_function` helpers.
We recommend the use of Unobtrusive JavaScript instead. For example:
- link_to "Greeting", "#", :class => "nav_link"
+ link_to "Greeting", "#", class: "nav_link"
$(function() {
$('.nav_link').click(function() {
@@ -363,11 +559,13 @@
* Remove `ActionDispatch::Head` middleware in favor of `Rack::Head`. *Santiago Pastorino*
-* Deprecate `:confirm` in favor of `:data => { :confirm => "Text" }` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers.
+* Deprecate `:confirm` in favor of `data: { confirm: "Text" }` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers.
*Carlos Galdino + Rafael Mendonça França*
-* Show routes in exception page while debugging a `RoutingError` in development. *Richard Schneeman and Mattt Thompson*
+* Show routes in exception page while debugging a `RoutingError` in development.
+
+ *Richard Schneeman + Mattt Thompson + Yves Senn*
* Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.:
@@ -375,7 +573,7 @@
add_flash_types :error, :warning
end
- If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, :error => 'message'` in a controller.
+ If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, error: 'message'` in a controller.
*kennyj*
@@ -417,7 +615,7 @@
*Sergey Nartimov*
-* change a way of ordering helpers from several directories. Previously,
+* 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
@@ -453,18 +651,18 @@
* Add `time_field` and `time_field_tag` helpers which render an `input[type="time"]` tag. *Alex Soulim*
-* Removed old text_helper apis for highlight, excerpt and word_wrap *Jeremy Walker*
+* Removed old text helper apis from `highlight`, `excerpt` and `word_wrap`. *Jeremy Walker*
* Templates without a handler extension now raises a deprecation warning but still
defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik*
-* Deprecate `:disable_with` in favor of `:data => { :disable_with => "Text" }` option from `submit_tag`, `button_tag` and `button_to` helpers.
+* Deprecate `:disable_with` in favor of `data: { disable_with: "Text" }` option from `submit_tag`, `button_tag` and `button_to` helpers.
*Carlos Galdino + Rafael Mendonça França*
* Remove `:mouseover` option from `image_tag` helper. *Rafael Mendonça França*
-* The `select` method (select tag) forces :include_blank if `required` is true and
+* The `select` method (select tag) forces `:include_blank` if `required` is true and
`display size` is one and `multiple` is not true. *Angelo Capilleri*
* Copy literal route constraints to defaults so that url generation know about them.
@@ -480,13 +678,13 @@
* Add backtrace to development routing error page. *Richard Schneeman*
-* Replace `include_seconds` boolean argument with `:include_seconds => true` option
+* Replace `include_seconds` boolean argument with `include_seconds: true` option
in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko*
* Make current object and counter (when it applies) variables accessible when
rendering templates with :object / :collection. *Carlos Antonio da Silva*
-* JSONP now uses mimetype text/javascript instead of application/json. *omjokine*
+* JSONP now uses mimetype `text/javascript` instead of `application/json`. *omjokine*
* Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki*
@@ -499,16 +697,16 @@
* Add `index` method to FormBuilder class. *Jorge Bejar*
-* Remove the leading \n added by textarea on assert_select. *Santiago Pastorino*
+* Remove the leading \n added by textarea on `assert_select`. *Santiago Pastorino*
* Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms`
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
- `:authenticity_token => true` in form options
+ `authenticity_token: true` in form options.
-* Added ActionDispatch::SSL middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França*
+* Added `ActionDispatch::SSL` middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França*
-* Add `include_hidden` option to select tag. With `:include_hidden => false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich*
+* Add `include_hidden` option to select tag. With `include_hidden: false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich*
* Removed default `size` option from the `text_field`, `search_field`, `telephone_field`, `url_field`, `email_field` helpers. *Philip Arndt*
@@ -525,7 +723,7 @@
* Don't ignore `force_ssl` in development. This is a change of behavior - use a `:if` condition to recreate the old behavior.
class AccountsController < ApplicationController
- force_ssl :if => :ssl_configured?
+ force_ssl if: :ssl_configured?
def ssl_configured?
!Rails.env.development?
@@ -594,15 +792,15 @@
*Carlos Antonio da Silva + Rafael Mendonça França*
-* check_box with `:form` html5 attribute will now replicate the `:form`
+* `check_box` with `:form` html5 attribute will now replicate the `:form`
attribute to the hidden field as well. *Carlos Antonio da Silva*
* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to
check that info. Closes #5245. *Santiago Pastorino*
-* `label` form helper accepts :for => nil to not generate the attribute. *Carlos Antonio da Silva*
+* `label` form helper accepts `for: nil` to not generate the attribute. *Carlos Antonio da Silva*
-* Add `:format` option to number_to_percentage *Rodrigo Flores*
+* Add `:format` option to `number_to_percentage`. *Rodrigo Flores*
* Add `config.action_view.logger` to configure logger for Action View. *Rafael Mendonça França*
@@ -622,7 +820,7 @@
* Deprecated `ActionController::Routing` in favour of `ActionDispatch::Routing`.
-* `check_box helper` with `:disabled => true` will generate a disabled
+* `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. *Tadas Tamosauskas*
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 810daf856c..5c668d9624 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2012 David Heinemeier Hansson
+Copyright (c) 2004-2013 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 50e3bb0d48..ba7956c3ab 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -15,7 +15,7 @@ Rake::TestTask.new(:test_action_pack) do |t|
# make sure we include the tests in alphabetical order as on some systems
# this will not happen automatically and the tests (as a whole) will error
- t.test_files = Dir.glob('test/{abstract,controller,dispatch,template,assertions}/**/*_test.rb').sort
+ t.test_files = Dir.glob('test/{abstract,controller,dispatch,template,assertions,journey}/**/*_test.rb').sort
t.warning = true
t.verbose = true
@@ -75,3 +75,9 @@ task :lines do
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
end
+
+rule '.rb' => '.y' do |t|
+ sh "racc -l -o #{t.name} #{t.source}"
+end
+
+task compile: 'lib/action_dispatch/journey/parser.rb'
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 89fdd528c2..c65870cac6 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
s.add_dependency 'builder', '~> 3.1.0'
s.add_dependency 'rack', '~> 1.4.1'
s.add_dependency 'rack-test', '~> 0.6.1'
- s.add_dependency 'journey', '~> 2.0.0'
s.add_dependency 'erubis', '~> 2.7.0'
s.add_development_dependency 'activemodel', version
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 02ac111392..599fff81c2 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -40,19 +40,22 @@ module AbstractController
end
end
- # Skip before, after, and around filters matching any of the names
+ # Skip before, after, and around action callbacks matching any of the names
+ # Aliased as skip_filter.
#
# ==== Parameters
# * <tt>names</tt> - A list of valid names that could be used for
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
# using #skip_filter
- def skip_filter(*names)
- skip_before_filter(*names)
- skip_after_filter(*names)
- skip_around_filter(*names)
+ def skip_action_callback(*names)
+ skip_before_action(*names)
+ skip_after_action(*names)
+ skip_around_action(*names)
end
+ alias_method :skip_filter, :skip_action_callback
+
# Take callback names and an optional callback proc, normalize them,
# then call the block with each callback. This allows us to abstract
# the normalization across several methods that use it.
@@ -75,119 +78,138 @@ module AbstractController
end
##
- # :method: before_filter
+ # :method: before_action
#
- # :call-seq: before_filter(names, block)
+ # :call-seq: before_action(names, block)
#
- # Append a before filter. See _insert_callbacks for parameter details.
+ # Append a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as before_filter.
##
- # :method: prepend_before_filter
+ # :method: prepend_before_action
#
- # :call-seq: prepend_before_filter(names, block)
+ # :call-seq: prepend_before_action(names, block)
#
- # Prepend a before filter. See _insert_callbacks for parameter details.
+ # Prepend a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as prepend_before_filter.
##
- # :method: skip_before_filter
+ # :method: skip_before_action
#
- # :call-seq: skip_before_filter(names)
+ # :call-seq: skip_before_action(names)
#
- # Skip a before filter. See _insert_callbacks for parameter details.
+ # Skip a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as skip_before_filter.
##
- # :method: append_before_filter
+ # :method: append_before_action
#
- # :call-seq: append_before_filter(names, block)
+ # :call-seq: append_before_action(names, block)
#
- # Append a before filter. See _insert_callbacks for parameter details.
+ # Append a callback before actions. See _insert_callbacks for parameter details.
+ # Aliased as append_before_filter.
##
- # :method: after_filter
+ # :method: after_action
#
- # :call-seq: after_filter(names, block)
+ # :call-seq: after_action(names, block)
#
- # Append an after filter. See _insert_callbacks for parameter details.
+ # Append a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as after_filter.
##
- # :method: prepend_after_filter
+ # :method: prepend_after_action
#
- # :call-seq: prepend_after_filter(names, block)
+ # :call-seq: prepend_after_action(names, block)
#
- # Prepend an after filter. See _insert_callbacks for parameter details.
+ # Prepend a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as prepend_after_filter.
##
- # :method: skip_after_filter
+ # :method: skip_after_action
#
- # :call-seq: skip_after_filter(names)
+ # :call-seq: skip_after_action(names)
#
- # Skip an after filter. See _insert_callbacks for parameter details.
+ # Skip a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as skip_after_filter.
##
- # :method: append_after_filter
+ # :method: append_after_action
#
- # :call-seq: append_after_filter(names, block)
+ # :call-seq: append_after_action(names, block)
#
- # Append an after filter. See _insert_callbacks for parameter details.
+ # Append a callback after actions. See _insert_callbacks for parameter details.
+ # Aliased as append_after_filter.
##
- # :method: around_filter
+ # :method: around_action
#
- # :call-seq: around_filter(names, block)
+ # :call-seq: around_action(names, block)
#
- # Append an around filter. See _insert_callbacks for parameter details.
+ # Append a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as around_filter.
##
- # :method: prepend_around_filter
+ # :method: prepend_around_action
#
- # :call-seq: prepend_around_filter(names, block)
+ # :call-seq: prepend_around_action(names, block)
#
- # Prepend an around filter. See _insert_callbacks for parameter details.
+ # Prepend a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as prepend_around_filter.
##
- # :method: skip_around_filter
+ # :method: skip_around_action
#
- # :call-seq: skip_around_filter(names)
+ # :call-seq: skip_around_action(names)
#
- # Skip an around filter. See _insert_callbacks for parameter details.
+ # Skip a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as skip_around_filter.
##
- # :method: append_around_filter
+ # :method: append_around_action
#
- # :call-seq: append_around_filter(names, block)
+ # :call-seq: append_around_action(names, block)
#
- # Append an around filter. See _insert_callbacks for parameter details.
+ # Append a callback around actions. See _insert_callbacks for parameter details.
+ # Aliased as append_around_filter.
- # set up before_filter, prepend_before_filter, skip_before_filter, etc.
+ # set up before_action, prepend_before_action, skip_before_action, etc.
# for each of before, after, and around.
- [:before, :after, :around].each do |filter|
+ [:before, :after, :around].each do |callback|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- # Append a before, after or around filter. See _insert_callbacks
+ # Append a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
- def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
+ def #{callback}_action(*names, &blk) # def before_action(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options)
+ end # end
+ end # end
+
+ alias_method :#{callback}_filter, :#{callback}_action
- # Prepend a before, after or around filter. See _insert_callbacks
+ # Prepend a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
- def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
- end # end
- end # end
+ def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
+ end # end
+ end # end
+
+ alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action
- # Skip a before, after or around filter. See _insert_callbacks
+ # Skip a before, after or around callback. See _insert_callbacks
# for details on the allowed parameters.
- def skip_#{filter}_filter(*names) # def skip_before_filter(*names)
- _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
- skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
- end # end
- end # end
-
- # *_filter is the same as append_*_filter
- alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter
+ def skip_#{callback}_action(*names) # def skip_before_action(*names)
+ _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
+ skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options)
+ end # end
+ end # end
+
+ alias_method :skip_#{callback}_filter, :skip_#{callback}_action
+
+ # *_action is the same as append_*_action
+ alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action
+ alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action
RUBY_EVAL
end
end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index d3929b685c..812a35735f 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -19,7 +19,7 @@ module AbstractController
def inherited(klass)
helpers = _helpers
klass._helpers = Module.new { include helpers }
- klass.class_eval { default_helper_module! unless anonymous? }
+ klass.class_eval { default_helper_module! } unless klass.anonymous?
super
end
@@ -58,11 +58,10 @@ module AbstractController
# The +helper+ class method can take a series of helper module names, a block, or both.
#
- # ==== Parameters
+ # ==== Options
# * <tt>*args</tt> - Module, Symbol, String, :all
# * <tt>block</tt> - A block defining helper methods
#
- # ==== Examples
# When the argument is a module it will be included directly in the template class.
# helper FooHelper # => includes FooHelper
#
@@ -114,7 +113,7 @@ module AbstractController
# helpers with the following behavior:
#
# String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
- # and "foo_bar_helper.rb" is loaded using require_dependency.
+ # and "foo_bar_helper.rb" is loaded using require_dependency.
#
# Module:: No further processing
#
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 12da273af9..91864f2a35 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -209,24 +209,26 @@ module AbstractController
_write_layout_method
end
- delegate :_layout_conditions, :to => "self.class"
+ delegate :_layout_conditions, to: :class
module ClassMethods
- def inherited(klass)
+ def inherited(klass) # :nodoc:
super
klass._write_layout_method
end
# This module is mixed in if layout conditions are provided. This means
# that if no layout conditions are used, this method is not used
- module LayoutConditions
- # Determines whether the current action has a layout by checking the
- # action name against the :only and :except conditions set on the
- # layout.
+ module LayoutConditions # :nodoc:
+ private
+
+ # Determines whether the current action has a layout definition by
+ # checking the action name against the :only and :except conditions
+ # set by the <tt>layout</tt> method.
#
# ==== Returns
- # * <tt> Boolean</tt> - True if the action has a layout, false otherwise.
- def conditional_layout?
+ # * <tt> Boolean</tt> - True if the action has a layout definition, false otherwise.
+ def _conditional_layout?
return unless super
conditions = _layout_conditions
@@ -271,7 +273,7 @@ module AbstractController
#
# ==== Returns
# * <tt>String</tt> - A template name
- def _implied_layout_name
+ def _implied_layout_name # :nodoc:
controller_path
end
@@ -279,7 +281,7 @@ module AbstractController
#
# If a layout is not explicitly mentioned then look for a layout with the controller's name.
# if nothing is found then try same procedure to find super class's layout.
- def _write_layout_method
+ def _write_layout_method # :nodoc:
remove_possible_method(:_layout)
prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
@@ -318,7 +320,7 @@ module AbstractController
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _layout
- if conditional_layout?
+ if _conditional_layout?
#{layout_definition}
else
#{name_clause}
@@ -329,7 +331,7 @@ module AbstractController
end
end
- def _normalize_options(options)
+ def _normalize_options(options) # :nodoc:
super
if _include_layout?(options)
@@ -340,21 +342,27 @@ module AbstractController
attr_internal_writer :action_has_layout
- def initialize(*)
+ def initialize(*) # :nodoc:
@_action_has_layout = true
super
end
+ # Controls whether an action should be rendered using a layout.
+ # If you want to disable any <tt>layout</tt> settings for the
+ # current action so that it is rendered without a layout then
+ # either override this method in your controller to return false
+ # for that action or set the <tt>action_has_layout</tt> attribute
+ # to false before rendering.
def action_has_layout?
@_action_has_layout
end
- def conditional_layout?
+ private
+
+ def _conditional_layout?
true
end
- private
-
# This will be overwritten by _write_layout_method
def _layout; end
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 462f147371..2892e093af 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -6,10 +6,9 @@ module ActionController
# \Caching is a cheap way of speeding up slow applications by keeping the result of
# calculations, renderings, and database calls around for subsequent requests.
#
- # You can read more about each approach and the sweeping assistance by clicking the
- # modules below.
+ # You can read more about each approach by clicking the modules below.
#
- # Note: To turn off all caching and sweeping, set
+ # Note: To turn off all caching, set
# config.action_controller.perform_caching = false.
#
# == \Caching stores
@@ -30,8 +29,6 @@ module ActionController
eager_autoload do
autoload :Fragments
- autoload :Sweeper, 'action_controller/caching/sweeping'
- autoload :Sweeping, 'action_controller/caching/sweeping'
end
module ConfigMethods
@@ -54,7 +51,6 @@ module ActionController
include ConfigMethods
include Fragments
- include Sweeping if defined?(ActiveRecord)
included do
extend ConfigMethods
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
deleted file mode 100644
index 317ac74b40..0000000000
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-module ActionController
- module Caching
- # Sweepers are the terminators of the caching world and responsible for expiring
- # caches when Active Record objects change. They do this by being half-observers,
- # half-filters and implementing callbacks for both roles.
- #
- # class ListSweeper < ActionController::Caching::Sweeper
- # observe List, Item
- #
- # def after_save(record)
- # list = record.is_a?(List) ? record : record.list
- # expire_page(controller: 'lists', action: %w( show public feed ), id: list.id)
- # expire_action(controller: 'lists', action: 'all')
- # list.shares.each { |share| expire_page(controller: 'lists', action: 'show', id: share.url_key) }
- # end
- # end
- #
- # The sweeper is assigned in the controllers that wish to have its job performed using
- # the +cache_sweeper+ class method:
- #
- # class ListsController < ApplicationController
- # caches_action :index, :show, :public, :feed
- # cache_sweeper :list_sweeper, only: [ :edit, :destroy, :share ]
- # end
- #
- # In the example above, four actions are cached and three actions are responsible for expiring those caches.
- #
- # You can also name an explicit class in the declaration of a sweeper, which is needed
- # if the sweeper is in a module:
- #
- # class ListsController < ApplicationController
- # caches_action :index, :show, :public, :feed
- # cache_sweeper OpenBar::Sweeper, only: [ :edit, :destroy, :share ]
- # end
- module Sweeping
- extend ActiveSupport::Concern
-
- module ClassMethods # :nodoc:
- def cache_sweeper(*sweepers)
- configuration = sweepers.extract_options!
-
- sweepers.each do |sweeper|
- ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
- sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance
-
- if sweeper_instance.is_a?(Sweeper)
- around_filter(sweeper_instance, :only => configuration[:only])
- else
- after_filter(sweeper_instance, :only => configuration[:only])
- end
- end
- end
- end
- end
-
- if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
- class Sweeper < ActiveRecord::Observer # :nodoc:
- attr_accessor :controller
-
- def initialize(*args)
- super
- @controller = nil
- end
-
- def before(controller)
- self.controller = controller
- callback(:before) if controller.perform_caching
- true # before method from sweeper should always return true
- end
-
- def after(controller)
- self.controller = controller
- callback(:after) if controller.perform_caching
- end
-
- def around(controller)
- before(controller)
- yield
- after(controller)
- ensure
- clean_up
- end
-
- protected
- # gets the action cache path for the given options.
- def action_path_for(options)
- Actions::ActionCachePath.new(controller, options).path
- end
-
- # Retrieve instance variables set in the controller.
- def assigns(key)
- controller.instance_variable_get("@#{key}")
- end
-
- private
- def clean_up
- # Clean up, so that the controller can be collected after this request
- self.controller = nil
- end
-
- def callback(timing)
- controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
- action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
-
- __send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
- __send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
- end
-
- def method_missing(method, *arguments, &block)
- return super unless @controller
- @controller.__send__(method, *arguments, &block)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 426adfe675..eddee08545 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/class/attribute'
-
module ActionController
module ConditionalGet
extend ActiveSupport::Concern
@@ -42,7 +40,7 @@ module ActionController
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cachable by other devices (proxy caches).
#
- # === Example:
+ # === Example:
#
# def show
# @article = Article.find(params[:id])
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 334943818c..75c4d3ef99 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -150,6 +150,7 @@ module ActionController #:nodoc:
disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
unless disposition.nil?
+ disposition = disposition.to_s
disposition += %(; filename="#{options[:filename]}") if options[:filename]
headers['Content-Disposition'] = disposition
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index c38d8ccef3..f1e8714a86 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -32,14 +32,14 @@ module ActionController
# ==== Options
# * <tt>host</tt> - Redirect to a different host name
# * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except</tt> - The callback should be run for all actions except this action
+ # * <tt>except</tt> - The callback should be run for all actions except this action
# * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
# will be called only when it returns a true value.
# * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
# will be called only when it returns a false value.
def force_ssl(options = {})
host = options.delete(:host)
- before_filter(options) do
+ before_action(options) do
force_ssl_redirect(host)
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index bbace49fd9..8237db15ca 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -31,6 +31,7 @@ module ActionController
if include_content?(self.status)
self.content_type = content_type || (Mime[formats.first] if formats)
+ self.response.charset = false if self.response
self.response_body = " "
else
headers.delete('Content-Type')
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index d2cbbd3330..35facd13c8 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -1,4 +1,3 @@
-
module ActionController
# The \Rails framework provides a large number of helpers for working with assets, dates, forms,
# numbers and model objects, to name a few. These helpers are available to all templates
@@ -91,11 +90,11 @@ module ActionController
end
def all_helpers_from_path(path)
- helpers = []
- Array(path).each do |_path|
- extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
+ helpers = Array(path).flat_map do |_path|
+ extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
- helpers += names.sort
+ names.sort!
+ names
end
helpers.uniq!
helpers
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index d3b5bafee1..896238b7dc 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -25,7 +25,7 @@ module ActionController
# the regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
- # before_filter :set_account, :authenticate
+ # before_action :set_account, :authenticate
#
# protected
# def set_account
@@ -68,7 +68,7 @@ module ActionController
module ClassMethods
def http_basic_authenticate_with(options = {})
- before_filter(options.except(:name, :password, :realm)) do
+ before_action(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
name == options[:name] && password == options[:password]
end
@@ -124,7 +124,7 @@ module ActionController
# USERS = {"dhh" => "secret", #plain text password
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
#
- # before_filter :authenticate, except: [:index]
+ # before_action :authenticate, except: [:index]
#
# def index
# render text: "Everyone can see me!"
@@ -317,7 +317,7 @@ module ActionController
# class PostsController < ApplicationController
# TOKEN = "secret"
#
- # before_filter :authenticate, except: [ :index ]
+ # before_action :authenticate, except: [ :index ]
#
# def index
# render text: "Everyone can see me!"
@@ -340,7 +340,7 @@ module ActionController
# the regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
- # before_filter :set_account, :authenticate
+ # before_action :set_account, :authenticate
#
# protected
# def set_account
@@ -384,6 +384,8 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
+ TOKEN_REGEX = /^Token /
+ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
module ControllerMethods
@@ -431,20 +433,34 @@ module ActionController
# Returns an Array of [String, Hash] if a token is present.
# Returns nil if no token is found.
def token_and_options(request)
- if request.authorization.to_s[/^Token (.*)/]
- values = Hash[$1.split(',').map do |value|
- value.strip! # remove any spaces between commas and values
- key, value = value.split(/\=\"?/) # split key=value pairs
- if value
- value.chomp!('"') # chomp trailing " in value
- value.gsub!(/\\\"/, '"') # unescape remaining quotes
- [key, value]
- end
- end.compact]
- [values.delete("token"), values.with_indifferent_access]
+ authorization_request = request.authorization.to_s
+ if authorization_request[TOKEN_REGEX]
+ params = token_params_from authorization_request
+ [params.shift.last, Hash[params].with_indifferent_access]
end
end
+ def token_params_from(auth)
+ rewrite_param_values params_array_from raw_params auth
+ end
+
+ # Takes raw_params and turns it into an array of parameters
+ def params_array_from(raw_params)
+ raw_params.map { |param| param.split %r/=(.+)?/ }
+ end
+
+ # This removes the `"` characters wrapping the value.
+ def rewrite_param_values(array_params)
+ array_params.each { |param| param.last.gsub! %r/^"|"$/, '' }
+ end
+
+ # This method takes an authorization body and splits up the key-value
+ # pairs by the standardized `:`, `;`, or `\t` delimiters defined in
+ # `AUTHN_PAIR_DELIMITERS`.
+ def raw_params(auth)
+ auth.sub(TOKEN_REGEX, '').split(/"\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+ end
+
# Encodes the given token and options into an Authorization header value.
#
# token - String token.
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index ca4ae532ca..d3aa8f90c5 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -60,7 +60,7 @@ module ActionController
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
result = super
payload[:status] = response.status
- payload[:location] = response.location
+ payload[:location] = response.filtered_location
result
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index a475d4bdff..c9f1d8dcb4 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/struct'
-require 'action_dispatch/http/mime_types'
+require 'action_dispatch/http/mime_type'
module ActionController
# Wraps the parameters hash into a nested hash. This will allow clients to submit
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index b23938e7d9..091facfd8d 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -74,7 +74,7 @@ module ActionController
private
def _extract_redirect_to_status(options, response_status)
- status = if options.is_a?(Hash) && options.key?(:status)
+ if options.is_a?(Hash) && options.key?(:status)
Rack::Utils.status_code(options.delete(:status))
elsif response_status.key?(:status)
Rack::Utils.status_code(response_status[:status])
@@ -94,8 +94,7 @@ module ActionController
when String
request.protocol + request.host_with_port + options
when :back
- raise RedirectBackError unless refer = request.headers["Referer"]
- refer
+ request.headers["Referer"] or raise RedirectBackError
when Proc
_compute_redirect_to_location options.call
else
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 265ce5d6f3..c5db0cb0d4 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -19,7 +19,7 @@ module ActionController #:nodoc:
#
# class ApplicationController < ActionController::Base
# protect_from_forgery
- # skip_before_filter :verify_authenticity_token, if: :json_request?
+ # skip_before_action :verify_authenticity_token, if: :json_request?
#
# protected
#
@@ -66,15 +66,15 @@ module ActionController #:nodoc:
#
# You can disable csrf protection on controller-by-controller basis:
#
- # skip_before_filter :verify_authenticity_token
+ # skip_before_action :verify_authenticity_token
#
# It can also be disabled for specific controller actions:
#
- # skip_before_filter :verify_authenticity_token, except: [:create]
+ # skip_before_action :verify_authenticity_token, except: [:create]
#
# Valid Options:
#
- # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
# * <tt>:with</tt> - Set the method to handle unverified request.
#
# Valid unverified request handling methods are:
@@ -84,7 +84,7 @@ module ActionController #:nodoc:
def protect_from_forgery(options = {})
include protection_method_module(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
- prepend_before_filter :verify_authenticity_token, options
+ prepend_before_action :verify_authenticity_token, options
end
private
@@ -152,7 +152,7 @@ module ActionController #:nodoc:
end
protected
- # The actual before_filter that is used. Modify this to change how you handle unverified requests.
+ # The actual before_action that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
logger.warn "Can't verify CSRF token authenticity" if logger
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 4eb582648e..0b3c438ec2 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -21,8 +21,6 @@ module ActionController #:nodoc:
# supports fibers (fibers are supported since version 1.9.2 of the main
# Ruby implementation).
#
- # == Examples
- #
# Streaming can be added to a given template easily, all you need to do is
# to pass the :stream option.
#
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index da640502a2..a158e6dbae 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -1,5 +1,5 @@
-require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/array/wrap'
require 'active_support/rescuable'
module ActionController
@@ -19,6 +19,20 @@ module ActionController
end
end
+ # Raised when a supplied parameter is not permitted.
+ #
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
+ class UnpermittedParameters < IndexError
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ super("found unpermitted keys: #{params.join(", ")}")
+ end
+ end
+
# == Action Controller \Parameters
#
# Allows to choose which attributes should be whitelisted for mass updating
@@ -40,13 +54,18 @@ module ActionController
# permitted.class # => ActionController::Parameters
# permitted.permitted? # => true
#
- # Person.first.update_attributes!(permitted)
+ # Person.first.update!(permitted)
# # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
#
- # It provides a +permit_all_parameters+ option that controls the top-level
- # behaviour of new instances. If it's +true+, all the parameters will be
+ # It provides two options that controls the top-level behavior of new instances:
+ #
+ # * +permit_all_parameters+ - If it's +true+, all the parameters will be
# permitted by default. The default value for +permit_all_parameters+
# option is +false+.
+ # * +raise_on_unpermitted_parameters+ - If it's +true+, it will raise an exception
+ # if parameters that are not explicitly permitted are found. The default value for
+ # +raise_on_unpermitted_parameters+ # option is +true+ in test and development
+ # environments, +false+ otherwise.
#
# params = ActionController::Parameters.new
# params.permitted? # => false
@@ -56,6 +75,16 @@ module ActionController
# params = ActionController::Parameters.new
# params.permitted? # => true
#
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => {}
+ #
+ # ActionController::Parameters.raise_on_unpermitted_parameters = true
+ #
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
+ #
# <tt>ActionController::Parameters</tt> is inherited from
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
@@ -65,13 +94,13 @@ module ActionController
# params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess
cattr_accessor :permit_all_parameters, instance_accessor: false
+ cattr_accessor :raise_on_unpermitted_parameters, instance_accessor: false
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
# <tt>ActionController::Parameters.permit_all_parameters</tt>.
#
- # class Person
- # include ActiveRecord::Base
+ # class Person < ActiveRecord::Base
# end
#
# params = ActionController::Parameters.new(name: 'Francesco')
@@ -125,10 +154,10 @@ module ActionController
# <tt>ActionController::ParameterMissing</tt> error.
#
# ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
- # # => {"name"=>"Francesco"}
+ # # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: nil).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
+ # # => ActionController::ParameterMissing: param not found: person
#
# ActionController::Parameters.new(person: {}).require(:person)
# # => ActionController::ParameterMissing: param not found: person
@@ -164,7 +193,7 @@ module ActionController
# }
# })
#
- # permitted = params.permit(person: [ :name, { pets: :name } ])
+ # permitted = params.permit(person: [ :name, { pets: :name } ])
# permitted.permitted? # => true
# permitted[:person][:name] # => "Francesco"
# permitted[:person][:age] # => nil
@@ -188,7 +217,7 @@ module ActionController
# # => {}
#
# params.require(:person).permit(contact: :phone)
- # # => {"contact"=>{"phone"=>"555-1234"}}
+ # # => {"contact"=>{"phone"=>"555-1234"}}
#
# params.require(:person).permit(contact: [ :email, :phone ])
# # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
@@ -204,6 +233,8 @@ module ActionController
end
keys.grep(/\A#{Regexp.escape(filter)}\(\d+[if]?\)\z/) { |key| params[key] = self[key] }
when Hash then
+ filter = filter.with_indifferent_access
+
self.slice(*filter.keys).each do |key, values|
return unless values
@@ -221,13 +252,20 @@ module ActionController
end
end
+ if Parameters.raise_on_unpermitted_parameters
+ unpermitted_keys = self.keys - params.keys
+ if unpermitted_keys.any?
+ raise ActionController::UnpermittedParameters.new(unpermitted_keys)
+ end
+ end
+
params.permit!
end
# Returns a parameter for the given +key+. If not found,
# returns +nil+.
#
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
+ # params = ActionController::Parameters.new(person: { name: 'Francesco' })
# params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
@@ -327,7 +365,7 @@ module ActionController
# # into a 400 Bad Request reply.
# def update
# redirect_to current_account.people.find(params[:id]).tap { |person|
- # person.update_attributes!(person_params)
+ # person.update!(person_params)
# }
# end
#
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 3e44155f73..731d66b0cf 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -20,22 +20,25 @@ module ActionController
end
initializer "action_controller.parameters_config" do |app|
- ActionController::Parameters.permit_all_parameters = app.config.action_controller.delete(:permit_all_parameters) { false }
+ options = app.config.action_controller
+
+ ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
+ ActionController::Parameters.raise_on_unpermitted_parameters = options.delete(:raise_on_unpermitted_parameters) { Rails.env.test? || Rails.env.development? }
end
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
options = app.config.action_controller
- options.logger ||= Rails.logger
- options.cache_store ||= Rails.cache
+ options.logger ||= Rails.logger
+ options.cache_store ||= Rails.cache
- options.javascripts_dir ||= paths["public/javascripts"].first
- options.stylesheets_dir ||= paths["public/stylesheets"].first
+ options.javascripts_dir ||= paths["public/javascripts"].first
+ options.stylesheets_dir ||= paths["public/stylesheets"].first
# Ensure readers methods get compiled
- options.asset_host ||= app.config.asset_host
- options.relative_url_root ||= app.config.relative_url_root
+ options.asset_host ||= app.config.asset_host
+ options.relative_url_root ||= app.config.relative_url_root
ActiveSupport.on_load(:action_controller) do
include app.routes.mounted_helpers
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index b49128c184..03b0f25f29 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -1,4 +1,3 @@
-require 'active_support/deprecation'
require 'action_view/record_identifier'
module ActionController
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 5aecb59df9..331d15d403 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -94,7 +94,7 @@ module ActionController
matches_template =
case options
when String
- rendered.any? do |t, num|
+ !options.empty? && rendered.any? do |t, num|
options_splited = options.split(File::SEPARATOR)
t_splited = t.split(File::SEPARATOR)
t_splited.last(options_splited.size) == options_splited
@@ -106,6 +106,8 @@ module ActionController
end
assert matches_template, msg
when Hash
+ options.assert_valid_keys(:layout, :partial, :locals, :count)
+
if options.key?(:layout)
expected_layout = options[:layout]
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
@@ -358,13 +360,6 @@ module ActionController
#
# assert_redirected_to page_url(title: 'foo')
class TestCase < ActiveSupport::TestCase
-
- # Use AC::TestCase for the base class when describing a controller
- register_spec_type(self) do |desc|
- Class === desc && desc < ActionController::Metal
- end
- register_spec_type(/Controller( ?Test)?\z/i, self)
-
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::TestProcess
@@ -509,7 +504,7 @@ module ActionController
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
@request.session.update(session) if session
- @request.session["flash"] = @request.flash.update(flash || {})
+ @request.flash.update(flash || {})
@controller.request = @request
@controller.response = @response
@@ -526,6 +521,7 @@ module ActionController
@response.prepare!
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
+ @request.session['flash'] = @request.flash.to_session_value
@request.session.delete('flash') if @request.session['flash'].blank?
@response
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 1d716a3248..b35761fb4a 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -63,6 +63,7 @@ module ActionDispatch
autoload :Static
end
+ autoload :Journey
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
@@ -75,6 +76,7 @@ module ActionDispatch
autoload :Parameters
autoload :ParameterFilter
autoload :FilterParameters
+ autoload :FilterRedirect
autoload :Upload
autoload :UploadedFile, 'action_dispatch/http/upload'
autoload :URL
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 4a7df6b657..02ab49b44e 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -1,4 +1,3 @@
-require 'mutex_m'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/duplicable'
@@ -21,8 +20,6 @@ module ActionDispatch
# end
# => reverses the value to all keys matching /secret/i
module FilterParameters
- @@parameter_filter_for = {}.extend(Mutex_m)
-
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
@@ -65,11 +62,7 @@ module ActionDispatch
end
def parameter_filter_for(filters)
- @@parameter_filter_for.synchronize do
- # Do we *actually* need this cache? Constructing ParameterFilters
- # doesn't seem too expensive.
- @@parameter_filter_for[filters] ||= ParameterFilter.new(filters)
- end
+ ParameterFilter.new(filters)
end
KV_RE = '[^&;=]+'
@@ -79,7 +72,6 @@ module ActionDispatch
parameter_filter.filter([[$1, $2]]).first.join("=")
end
end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb
new file mode 100644
index 0000000000..900ce1c646
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb
@@ -0,0 +1,37 @@
+module ActionDispatch
+ module Http
+ module FilterRedirect
+
+ FILTERED = '[FILTERED]'.freeze # :nodoc:
+
+ def filtered_location
+ if !location_filter.empty? && location_filter_match?
+ FILTERED
+ else
+ location
+ end
+ end
+
+ private
+
+ def location_filter
+ if request.present?
+ request.env['action_dispatch.redirect_filter'] || []
+ else
+ []
+ end
+ end
+
+ def location_filter_match?
+ location_filter.any? do |filter|
+ if String === filter
+ location.include?(filter)
+ elsif Regexp === filter
+ location.match(filter)
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 0f98e84788..57660e93c4 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -68,7 +68,7 @@ module ActionDispatch
# that are not controlled by the extension.
#
# class ApplicationController < ActionController::Base
- # before_filter :adjust_format_for_iphone
+ # before_action :adjust_format_for_iphone
#
# private
# def adjust_format_for_iphone
@@ -87,7 +87,7 @@ module ActionDispatch
# to the :html format.
#
# class ApplicationController < ActionController::Base
- # before_filter :adjust_format_for_iphone_with_html_fallback
+ # before_action :adjust_format_for_iphone_with_html_fallback
#
# private
# def adjust_format_for_iphone_with_html_fallback
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index f56f09c5b3..912da741b7 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -27,7 +27,7 @@ module Mime
class << self
def [](type)
return type if type.is_a?(Type)
- Type.lookup_by_extension(type)
+ Type.lookup_by_extension(type) || NullType.new
end
def fetch(type)
@@ -306,6 +306,17 @@ module Mime
method.to_s.ends_with? '?'
end
end
+
+ class NullType
+ def nil?
+ true
+ end
+
+ private
+ def method_missing(method, *args)
+ false if method.to_s.ends_with? '?'
+ end
+ end
end
require 'action_dispatch/http/mime_types'
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index 9a7b5bc8c7..6610315da7 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -12,7 +12,11 @@ module ActionDispatch
# Returns both GET and POST \parameters in a single hash.
def parameters
@env["action_dispatch.request.parameters"] ||= begin
- params = request_parameters.merge(query_parameters)
+ params = begin
+ request_parameters.merge(query_parameters)
+ rescue EOFError
+ query_parameters.dup
+ end
params.merge!(path_parameters)
encode_params(params).with_indifferent_access
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 3de927abc8..452809a689 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -1,9 +1,5 @@
-require 'tempfile'
require 'stringio'
-require 'strscan'
-require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/string/access'
require 'active_support/inflector'
require 'action_dispatch/http/headers'
require 'action_controller/metal/exceptions'
@@ -205,8 +201,9 @@ module ActionDispatch
# work with raw requests directly.
def raw_post
unless @env.include? 'RAW_POST_DATA'
- @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
- body.rewind if body.respond_to?(:rewind)
+ raw_post_body = body
+ @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i)
+ raw_post_body.rewind if raw_post_body.respond_to?(:rewind)
end
@env['RAW_POST_DATA']
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 11b7534ea4..91cf4784db 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -61,6 +61,7 @@ module ActionDispatch # :nodoc:
cattr_accessor(:default_headers)
include Rack::Response::Helpers
+ include ActionDispatch::Http::FilterRedirect
include ActionDispatch::Http::Cache::Response
include MonitorMixin
@@ -259,14 +260,18 @@ module ActionDispatch # :nodoc:
return if headers[CONTENT_TYPE].present?
@content_type ||= Mime::HTML
- @charset ||= self.class.default_charset
+ @charset ||= self.class.default_charset unless @charset == false
type = @content_type.to_s.dup
- type << "; charset=#{@charset}" unless @sending_file
+ type << "; charset=#{@charset}" if append_charset?
headers[CONTENT_TYPE] = type
end
+ def append_charset?
+ !@sending_file && @charset != false
+ end
+
def rack_response(status, header)
assign_default_content_type_and_charset!(header)
handle_conditional_get!
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 79437d6e85..f9b007b57b 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -19,7 +19,7 @@ module ActionDispatch
# its interface is available directly.
attr_accessor :tempfile
- # TODO.
+ # A string with the headers of the multipart request.
attr_accessor :headers
def initialize(hash) # :nodoc:
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 9a7e8a5a9c..43f26d696d 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -28,11 +28,15 @@ module ActionDispatch
path = options.delete(:script_name).to_s.chomp("/")
path << options.delete(:path).to_s
- params = options[:params] || {}
+ params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
params.reject! { |_,v| v.to_param.nil? }
result = build_host_url(options)
- result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ if options[:trailing_slash] && !path.ends_with?('/')
+ result << path.sub(/(\?|\z)/) { "/" + $& }
+ else
+ result << path
+ end
result << "?#{params.to_query}" unless params.empty?
result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
result
diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb
new file mode 100644
index 0000000000..ad42713482
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey.rb
@@ -0,0 +1,5 @@
+require 'action_dispatch/journey/router'
+require 'action_dispatch/journey/gtg/builder'
+require 'action_dispatch/journey/gtg/simulator'
+require 'action_dispatch/journey/nfa/builder'
+require 'action_dispatch/journey/nfa/simulator'
diff --git a/actionpack/lib/action_dispatch/journey/backwards.rb b/actionpack/lib/action_dispatch/journey/backwards.rb
new file mode 100644
index 0000000000..3bd20fdf81
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/backwards.rb
@@ -0,0 +1,5 @@
+module Rack # :nodoc:
+ Mount = ActionDispatch::Journey::Router
+ Mount::RouteSet = ActionDispatch::Journey::Router
+ Mount::RegexpWithNamedGroups = ActionDispatch::Journey::Path::Pattern
+end
diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb
new file mode 100644
index 0000000000..cf755bfbeb
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/formatter.rb
@@ -0,0 +1,141 @@
+module ActionDispatch
+ module Journey
+ # The Formatter class is used for formatting URLs. For example, parameters
+ # passed to +url_for+ in rails will eventually call Formatter#generate.
+ class Formatter # :nodoc:
+ attr_reader :routes
+
+ def initialize(routes)
+ @routes = routes
+ @cache = nil
+ end
+
+ def generate(type, name, options, recall = {}, parameterize = nil)
+ constraints = recall.merge(options)
+ missing_keys = []
+
+ match_route(name, constraints) do |route|
+ parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
+ next if !name && route.requirements.empty? && route.parts.empty?
+
+ missing_keys = missing_keys(route, parameterized_parts)
+ next unless missing_keys.empty?
+ params = options.dup.delete_if do |key, _|
+ parameterized_parts.key?(key) || route.defaults.key?(key)
+ end
+
+ return [route.format(parameterized_parts), params]
+ end
+
+ raise Router::RoutingError.new "missing required keys: #{missing_keys}"
+ end
+
+ def clear
+ @cache = nil
+ end
+
+ private
+
+ def extract_parameterized_parts(route, options, recall, parameterize = nil)
+ parameterized_parts = recall.merge(options)
+
+ keys_to_keep = route.parts.reverse.drop_while { |part|
+ !options.key?(part) || (options[part] || recall[part]).nil?
+ } | route.required_parts
+
+ (parameterized_parts.keys - keys_to_keep).each do |bad_key|
+ parameterized_parts.delete(bad_key)
+ end
+
+ if parameterize
+ parameterized_parts.each do |k, v|
+ parameterized_parts[k] = parameterize.call(k, v)
+ end
+ end
+
+ parameterized_parts.keep_if { |_, v| v }
+ parameterized_parts
+ end
+
+ def named_routes
+ routes.named_routes
+ end
+
+ def match_route(name, options)
+ if named_routes.key?(name)
+ yield named_routes[name]
+ else
+ routes = non_recursive(cache, options.to_a)
+
+ hash = routes.group_by { |_, r| r.score(options) }
+
+ hash.keys.sort.reverse_each do |score|
+ next if score < 0
+
+ hash[score].sort_by { |i, _| i }.each do |_, route|
+ yield route
+ end
+ end
+ end
+ end
+
+ def non_recursive(cache, options)
+ routes = []
+ stack = [cache]
+
+ while stack.any?
+ c = stack.shift
+ routes.concat(c[:___routes]) if c.key?(:___routes)
+
+ options.each do |pair|
+ stack << c[pair] if c.key?(pair)
+ end
+ end
+
+ routes
+ end
+
+ # Returns an array populated with missing keys if any are present.
+ def missing_keys(route, parts)
+ missing_keys = []
+ tests = route.path.requirements
+ route.required_parts.each { |key|
+ if tests.key?(key)
+ missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
+ else
+ missing_keys << key unless parts[key]
+ end
+ }
+ missing_keys
+ end
+
+ def possibles(cache, options, depth = 0)
+ cache.fetch(:___routes) { [] } + options.find_all { |pair|
+ cache.key?(pair)
+ }.map { |pair|
+ possibles(cache[pair], options, depth + 1)
+ }.flatten(1)
+ end
+
+ # Returns +true+ if no missing keys are present, otherwise +false+.
+ def verify_required_parts!(route, parts)
+ missing_keys(route, parts).empty?
+ end
+
+ def build_cache
+ root = { ___routes: [] }
+ routes.each_with_index do |route, i|
+ leaf = route.required_defaults.inject(root) do |h, tuple|
+ h[tuple] ||= {}
+ end
+ (leaf[:___routes] ||= []) << [i, route]
+ end
+ root
+ end
+
+ def cache
+ @cache ||= build_cache
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
new file mode 100644
index 0000000000..7d2791714b
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
@@ -0,0 +1,162 @@
+require 'action_dispatch/journey/gtg/transition_table'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module GTG # :nodoc:
+ class Builder # :nodoc:
+ DUMMY = Nodes::Dummy.new
+
+ attr_reader :root, :ast, :endpoints
+
+ def initialize(root)
+ @root = root
+ @ast = Nodes::Cat.new root, DUMMY
+ @followpos = nil
+ end
+
+ def transition_table
+ dtrans = TransitionTable.new
+ marked = {}
+ state_id = Hash.new { |h,k| h[k] = h.length }
+
+ start = firstpos(root)
+ dstates = [start]
+ until dstates.empty?
+ s = dstates.shift
+ next if marked[s]
+ marked[s] = true # mark s
+
+ s.group_by { |state| symbol(state) }.each do |sym, ps|
+ u = ps.map { |l| followpos(l) }.flatten
+ next if u.empty?
+
+ if u.uniq == [DUMMY]
+ from = state_id[s]
+ to = state_id[Object.new]
+ dtrans[from, to] = sym
+
+ dtrans.add_accepting(to)
+ ps.each { |state| dtrans.add_memo(to, state.memo) }
+ else
+ dtrans[state_id[s], state_id[u]] = sym
+
+ if u.include?(DUMMY)
+ to = state_id[u]
+
+ accepting = ps.find_all { |l| followpos(l).include?(DUMMY) }
+
+ accepting.each { |accepting_state|
+ dtrans.add_memo(to, accepting_state.memo)
+ }
+
+ dtrans.add_accepting(state_id[u])
+ end
+ end
+
+ dstates << u
+ end
+ end
+
+ dtrans
+ end
+
+ def nullable?(node)
+ case node
+ when Nodes::Group
+ true
+ when Nodes::Star
+ true
+ when Nodes::Or
+ node.children.any? { |c| nullable?(c) }
+ when Nodes::Cat
+ nullable?(node.left) && nullable?(node.right)
+ when Nodes::Terminal
+ !node.left
+ when Nodes::Unary
+ nullable?(node.left)
+ else
+ raise ArgumentError, 'unknown nullable: %s' % node.class.name
+ end
+ end
+
+ def firstpos(node)
+ case node
+ when Nodes::Star
+ firstpos(node.left)
+ when Nodes::Cat
+ if nullable?(node.left)
+ firstpos(node.left) | firstpos(node.right)
+ else
+ firstpos(node.left)
+ end
+ when Nodes::Or
+ node.children.map { |c| firstpos(c) }.flatten.uniq
+ when Nodes::Unary
+ firstpos(node.left)
+ when Nodes::Terminal
+ nullable?(node) ? [] : [node]
+ else
+ raise ArgumentError, 'unknown firstpos: %s' % node.class.name
+ end
+ end
+
+ def lastpos(node)
+ case node
+ when Nodes::Star
+ firstpos(node.left)
+ when Nodes::Or
+ node.children.map { |c| lastpos(c) }.flatten.uniq
+ when Nodes::Cat
+ if nullable?(node.right)
+ lastpos(node.left) | lastpos(node.right)
+ else
+ lastpos(node.right)
+ end
+ when Nodes::Terminal
+ nullable?(node) ? [] : [node]
+ when Nodes::Unary
+ lastpos(node.left)
+ else
+ raise ArgumentError, 'unknown lastpos: %s' % node.class.name
+ end
+ end
+
+ def followpos(node)
+ followpos_table[node]
+ end
+
+ private
+
+ def followpos_table
+ @followpos ||= build_followpos
+ end
+
+ def build_followpos
+ table = Hash.new { |h, k| h[k] = [] }
+ @ast.each do |n|
+ case n
+ when Nodes::Cat
+ lastpos(n.left).each do |i|
+ table[i] += firstpos(n.right)
+ end
+ when Nodes::Star
+ lastpos(n).each do |i|
+ table[i] += firstpos(n)
+ end
+ end
+ end
+ table
+ end
+
+ def symbol(edge)
+ case edge
+ when Journey::Nodes::Symbol
+ edge.regexp
+ else
+ edge.left
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
new file mode 100644
index 0000000000..58ad803841
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb
@@ -0,0 +1,44 @@
+require 'strscan'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module GTG # :nodoc:
+ class MatchData # :nodoc:
+ attr_reader :memos
+
+ def initialize(memos)
+ @memos = memos
+ end
+ end
+
+ class Simulator # :nodoc:
+ attr_reader :tt
+
+ def initialize(transition_table)
+ @tt = transition_table
+ end
+
+ def simulate(string)
+ input = StringScanner.new(string)
+ state = [0]
+ while sym = input.scan(%r([/.?]|[^/.?]+))
+ state = tt.move(state, sym)
+ end
+
+ acceptance_states = state.find_all { |s|
+ tt.accepting? s
+ }
+
+ return if acceptance_states.empty?
+
+ memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
+
+ MatchData.new(memos)
+ end
+
+ alias :=~ :simulate
+ alias :match :simulate
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
new file mode 100644
index 0000000000..da0cddd93c
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -0,0 +1,156 @@
+require 'action_dispatch/journey/nfa/dot'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module GTG # :nodoc:
+ class TransitionTable # :nodoc:
+ include Journey::NFA::Dot
+
+ attr_reader :memos
+
+ def initialize
+ @regexp_states = Hash.new { |h,k| h[k] = {} }
+ @string_states = Hash.new { |h,k| h[k] = {} }
+ @accepting = {}
+ @memos = Hash.new { |h,k| h[k] = [] }
+ end
+
+ def add_accepting(state)
+ @accepting[state] = true
+ end
+
+ def accepting_states
+ @accepting.keys
+ end
+
+ def accepting?(state)
+ @accepting[state]
+ end
+
+ def add_memo(idx, memo)
+ @memos[idx] << memo
+ end
+
+ def memo(idx)
+ @memos[idx]
+ end
+
+ def eclosure(t)
+ Array(t)
+ end
+
+ def move(t, a)
+ move_string(t, a).concat(move_regexp(t, a))
+ end
+
+ def to_json
+ require 'json'
+
+ simple_regexp = Hash.new { |h,k| h[k] = {} }
+
+ @regexp_states.each do |from, hash|
+ hash.each do |re, to|
+ simple_regexp[from][re.source] = to
+ end
+ end
+
+ JSON.dump({
+ regexp_states: simple_regexp,
+ string_states: @string_states,
+ accepting: @accepting
+ })
+ end
+
+ def to_svg
+ svg = IO.popen('dot -Tsvg', 'w+') { |f|
+ f.write(to_dot)
+ f.close_write
+ f.readlines
+ }
+ 3.times { svg.shift }
+ svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
+ end
+
+ def visualizer(paths, title = 'FSM')
+ viz_dir = File.join File.dirname(__FILE__), '..', 'visualizer'
+ fsm_js = File.read File.join(viz_dir, 'fsm.js')
+ fsm_css = File.read File.join(viz_dir, 'fsm.css')
+ erb = File.read File.join(viz_dir, 'index.html.erb')
+ states = "function tt() { return #{to_json}; }"
+
+ fun_routes = paths.shuffle.first(3).map do |ast|
+ ast.map { |n|
+ case n
+ when Nodes::Symbol
+ case n.left
+ when ':id' then rand(100).to_s
+ when ':format' then %w{ xml json }.shuffle.first
+ else
+ 'omg'
+ end
+ when Nodes::Terminal then n.symbol
+ else
+ nil
+ end
+ }.compact.join
+ end
+
+ stylesheets = [fsm_css]
+ svg = to_svg
+ javascripts = [states, fsm_js]
+
+ # Annoying hack for 1.9 warnings
+ fun_routes = fun_routes
+ stylesheets = stylesheets
+ svg = svg
+ javascripts = javascripts
+
+ require 'erb'
+ template = ERB.new erb
+ template.result(binding)
+ end
+
+ def []=(from, to, sym)
+ case sym
+ when String
+ @string_states[from][sym] = to
+ when Regexp
+ @regexp_states[from][sym] = to
+ else
+ raise ArgumentError, 'unknown symbol: %s' % sym.class
+ end
+ end
+
+ def states
+ ss = @string_states.keys + @string_states.values.map(&:values).flatten
+ rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
+ (ss + rs).uniq
+ end
+
+ def transitions
+ @string_states.map { |from, hash|
+ hash.map { |s, to| [from, s, to] }
+ }.flatten(1) + @regexp_states.map { |from, hash|
+ hash.map { |s, to| [from, s, to] }
+ }.flatten(1)
+ end
+
+ private
+
+ def move_regexp(t, a)
+ return [] if t.empty?
+
+ t.map { |s|
+ @regexp_states[s].map { |re, v| re === a ? v : nil }
+ }.flatten.compact.uniq
+ end
+
+ def move_string(t, a)
+ return [] if t.empty?
+
+ t.map { |s| @string_states[s][a] }.compact
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
new file mode 100644
index 0000000000..ee6494c3e4
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
@@ -0,0 +1,76 @@
+require 'action_dispatch/journey/nfa/transition_table'
+require 'action_dispatch/journey/gtg/transition_table'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ class Visitor < Visitors::Visitor # :nodoc:
+ def initialize(tt)
+ @tt = tt
+ @i = -1
+ end
+
+ def visit_CAT(node)
+ left = visit(node.left)
+ right = visit(node.right)
+
+ @tt.merge(left.last, right.first)
+
+ [left.first, right.last]
+ end
+
+ def visit_GROUP(node)
+ from = @i += 1
+ left = visit(node.left)
+ to = @i += 1
+
+ @tt.accepting = to
+
+ @tt[from, left.first] = nil
+ @tt[left.last, to] = nil
+ @tt[from, to] = nil
+
+ [from, to]
+ end
+
+ def visit_OR(node)
+ from = @i += 1
+ children = node.children.map { |c| visit(c) }
+ to = @i += 1
+
+ children.each do |child|
+ @tt[from, child.first] = nil
+ @tt[child.last, to] = nil
+ end
+
+ @tt.accepting = to
+
+ [from, to]
+ end
+
+ def terminal(node)
+ from_i = @i += 1 # new state
+ to_i = @i += 1 # new state
+
+ @tt[from_i, to_i] = node
+ @tt.accepting = to_i
+ @tt.add_memo(to_i, node.memo)
+
+ [from_i, to_i]
+ end
+ end
+
+ class Builder # :nodoc:
+ def initialize(ast)
+ @ast = ast
+ end
+
+ def transition_table
+ tt = TransitionTable.new
+ Visitor.new(tt).accept(@ast)
+ tt
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
new file mode 100644
index 0000000000..5c33a872e5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ module Dot # :nodoc:
+ def to_dot
+ edges = transitions.map { |from, sym, to|
+ " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
+ }
+
+ #memo_nodes = memos.values.flatten.map { |n|
+ # label = n
+ # if Journey::Route === n
+ # label = "#{n.verb.source} #{n.path.spec}"
+ # end
+ # " #{n.object_id} [label=\"#{label}\", shape=box];"
+ #}
+ #memo_edges = memos.map { |k, memos|
+ # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
+ #}.flatten.uniq
+
+ <<-eodot
+digraph nfa {
+ rankdir=LR;
+ node [shape = doublecircle];
+ #{accepting_states.join ' '};
+ node [shape = circle];
+#{edges.join "\n"}
+}
+ eodot
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/simulator.rb b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
new file mode 100644
index 0000000000..5b40da6569
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/simulator.rb
@@ -0,0 +1,47 @@
+require 'strscan'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ class MatchData # :nodoc:
+ attr_reader :memos
+
+ def initialize(memos)
+ @memos = memos
+ end
+ end
+
+ class Simulator # :nodoc:
+ attr_reader :tt
+
+ def initialize(transition_table)
+ @tt = transition_table
+ end
+
+ def simulate(string)
+ input = StringScanner.new(string)
+ state = tt.eclosure(0)
+ until input.eos?
+ sym = input.scan(%r([/.?]|[^/.?]+))
+
+ # FIXME: tt.eclosure is not needed for the GTG
+ state = tt.eclosure(tt.move(state, sym))
+ end
+
+ acceptance_states = state.find_all { |s|
+ tt.accepting?(tt.eclosure(s).sort.last)
+ }
+
+ return if acceptance_states.empty?
+
+ memos = acceptance_states.map { |x| tt.memo(x) }.flatten.compact
+
+ MatchData.new(memos)
+ end
+
+ alias :=~ :simulate
+ alias :match :simulate
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
new file mode 100644
index 0000000000..a3017aeea1
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -0,0 +1,163 @@
+require 'action_dispatch/journey/nfa/dot'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module NFA # :nodoc:
+ class TransitionTable # :nodoc:
+ include Journey::NFA::Dot
+
+ attr_accessor :accepting
+ attr_reader :memos
+
+ def initialize
+ @table = Hash.new { |h,f| h[f] = {} }
+ @memos = {}
+ @accepting = nil
+ @inverted = nil
+ end
+
+ def accepting?(state)
+ accepting == state
+ end
+
+ def accepting_states
+ [accepting]
+ end
+
+ def add_memo(idx, memo)
+ @memos[idx] = memo
+ end
+
+ def memo(idx)
+ @memos[idx]
+ end
+
+ def []=(i, f, s)
+ @table[f][i] = s
+ end
+
+ def merge(left, right)
+ @memos[right] = @memos.delete(left)
+ @table[right] = @table.delete(left)
+ end
+
+ def states
+ (@table.keys + @table.values.map(&:keys).flatten).uniq
+ end
+
+ # Returns a generalized transition graph with reduced states. The states
+ # are reduced like a DFA, but the table must be simulated like an NFA.
+ #
+ # Edges of the GTG are regular expressions.
+ def generalized_table
+ gt = GTG::TransitionTable.new
+ marked = {}
+ state_id = Hash.new { |h,k| h[k] = h.length }
+ alphabet = self.alphabet
+
+ stack = [eclosure(0)]
+
+ until stack.empty?
+ state = stack.pop
+ next if marked[state] || state.empty?
+
+ marked[state] = true
+
+ alphabet.each do |alpha|
+ next_state = eclosure(following_states(state, alpha))
+ next if next_state.empty?
+
+ gt[state_id[state], state_id[next_state]] = alpha
+ stack << next_state
+ end
+ end
+
+ final_groups = state_id.keys.find_all { |s|
+ s.sort.last == accepting
+ }
+
+ final_groups.each do |states|
+ id = state_id[states]
+
+ gt.add_accepting(id)
+ save = states.find { |s|
+ @memos.key?(s) && eclosure(s).sort.last == accepting
+ }
+
+ gt.add_memo(id, memo(save))
+ end
+
+ gt
+ end
+
+ # Returns set of NFA states to which there is a transition on ast symbol
+ # +a+ from some state +s+ in +t+.
+ def following_states(t, a)
+ Array(t).map { |s| inverted[s][a] }.flatten.uniq
+ end
+
+ # Returns set of NFA states to which there is a transition on ast symbol
+ # +a+ from some state +s+ in +t+.
+ def move(t, a)
+ Array(t).map { |s|
+ inverted[s].keys.compact.find_all { |sym|
+ sym === a
+ }.map { |sym| inverted[s][sym] }
+ }.flatten.uniq
+ end
+
+ def alphabet
+ inverted.values.map(&:keys).flatten.compact.uniq.sort_by { |x| x.to_s }
+ end
+
+ # Returns a set of NFA states reachable from some NFA state +s+ in set
+ # +t+ on nil-transitions alone.
+ def eclosure(t)
+ stack = Array(t)
+ seen = {}
+ children = []
+
+ until stack.empty?
+ s = stack.pop
+ next if seen[s]
+
+ seen[s] = true
+ children << s
+
+ stack.concat(inverted[s][nil])
+ end
+
+ children.uniq
+ end
+
+ def transitions
+ @table.map { |to, hash|
+ hash.map { |from, sym| [from, sym, to] }
+ }.flatten(1)
+ end
+
+ private
+
+ def inverted
+ return @inverted if @inverted
+
+ @inverted = Hash.new { |h, from|
+ h[from] = Hash.new { |j, s| j[s] = [] }
+ }
+
+ @table.each { |to, hash|
+ hash.each { |from, sym|
+ if sym
+ sym = Nodes::Symbol === sym ? sym.regexp : sym.left
+ end
+
+ @inverted[from][sym] << to
+ }
+ }
+
+ @inverted
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
new file mode 100644
index 0000000000..935442ef66
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -0,0 +1,124 @@
+require 'action_dispatch/journey/visitors'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ module Nodes # :nodoc:
+ class Node # :nodoc:
+ include Enumerable
+
+ attr_accessor :left, :memo
+
+ def initialize(left)
+ @left = left
+ @memo = nil
+ end
+
+ def each(&block)
+ Visitors::Each.new(block).accept(self)
+ end
+
+ def to_s
+ Visitors::String.new.accept(self)
+ end
+
+ def to_dot
+ Visitors::Dot.new.accept(self)
+ end
+
+ def to_sym
+ name.to_sym
+ end
+
+ def name
+ left.tr '*:', ''
+ end
+
+ def type
+ raise NotImplementedError
+ end
+
+ def symbol?; false; end
+ def literal?; false; end
+ end
+
+ class Terminal < Node # :nodoc:
+ alias :symbol :left
+ end
+
+ class Literal < Terminal # :nodoc:
+ def literal?; true; end
+ def type; :LITERAL; end
+ end
+
+ class Dummy < Literal # :nodoc:
+ def initialize(x = Object.new)
+ super
+ end
+
+ def literal?; false; end
+ end
+
+ %w{ Symbol Slash Dot }.each do |t|
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
+ class #{t} < Terminal;
+ def type; :#{t.upcase}; end
+ end
+ eoruby
+ end
+
+ class Symbol < Terminal # :nodoc:
+ attr_accessor :regexp
+ alias :symbol :regexp
+
+ DEFAULT_EXP = /[^\.\/\?]+/
+ def initialize(left)
+ super
+ @regexp = DEFAULT_EXP
+ end
+
+ def default_regexp?
+ regexp == DEFAULT_EXP
+ end
+
+ def symbol?; true; end
+ end
+
+ class Unary < Node # :nodoc:
+ def children; [left] end
+ end
+
+ class Group < Unary # :nodoc:
+ def type; :GROUP; end
+ end
+
+ class Star < Unary # :nodoc:
+ def type; :STAR; end
+ end
+
+ class Binary < Node # :nodoc:
+ attr_accessor :right
+
+ def initialize(left, right)
+ super(left)
+ @right = right
+ end
+
+ def children; [left, right] end
+ end
+
+ class Cat < Binary # :nodoc:
+ def type; :CAT; end
+ end
+
+ class Or < Node # :nodoc:
+ attr_reader :children
+
+ def initialize(children)
+ @children = children
+ end
+
+ def type; :OR; end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
new file mode 100644
index 0000000000..bb4cbb00e2
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -0,0 +1,206 @@
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by Racc 1.4.9
+# from Racc grammer file "".
+#
+
+require 'racc/parser.rb'
+
+
+require 'action_dispatch/journey/parser_extras'
+module ActionDispatch
+ module Journey # :nodoc:
+ class Parser < Racc::Parser # :nodoc:
+##### State transition tables begin ###
+
+racc_action_table = [
+ 17, 21, 13, 15, 14, 7, nil, 16, 8, 19,
+ 13, 15, 14, 7, 23, 16, 8, 19, 13, 15,
+ 14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
+ 16, 8, 13, 15, 14, 7, nil, 16, 8 ]
+
+racc_action_check = [
+ 1, 17, 1, 1, 1, 1, nil, 1, 1, 1,
+ 20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
+ 7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
+ 19, 19, 0, 0, 0, 0, nil, 0, 0 ]
+
+racc_action_pointer = [
+ 30, 0, nil, nil, nil, nil, nil, 16, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
+ 8, nil, nil, nil ]
+
+racc_action_default = [
+ -18, -18, -2, -3, -4, -5, -6, -18, -9, -10,
+ -11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
+ -18, 24, -8, -7 ]
+
+racc_goto_table = [
+ 18, 1, nil, nil, nil, nil, nil, nil, 20, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 22, 18 ]
+
+racc_goto_check = [
+ 2, 1, nil, nil, nil, nil, nil, nil, 1, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
+
+racc_goto_pointer = [
+ nil, 1, -1, nil, nil, nil, nil, nil, nil, nil,
+ nil ]
+
+racc_goto_default = [
+ nil, nil, 2, 3, 4, 5, 6, 9, 10, 11,
+ 12 ]
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 2, 11, :_reduce_1,
+ 1, 11, :_reduce_2,
+ 1, 11, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 1, 12, :_reduce_none,
+ 3, 15, :_reduce_7,
+ 3, 13, :_reduce_8,
+ 1, 16, :_reduce_9,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 14, :_reduce_none,
+ 1, 19, :_reduce_14,
+ 1, 17, :_reduce_15,
+ 1, 18, :_reduce_16,
+ 1, 20, :_reduce_17 ]
+
+racc_reduce_n = 18
+
+racc_shift_n = 24
+
+racc_token_table = {
+ false => 0,
+ :error => 1,
+ :SLASH => 2,
+ :LITERAL => 3,
+ :SYMBOL => 4,
+ :LPAREN => 5,
+ :RPAREN => 6,
+ :DOT => 7,
+ :STAR => 8,
+ :OR => 9 }
+
+racc_nt_base = 10
+
+racc_use_result_var = true
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+ "$end",
+ "error",
+ "SLASH",
+ "LITERAL",
+ "SYMBOL",
+ "LPAREN",
+ "RPAREN",
+ "DOT",
+ "STAR",
+ "OR",
+ "$start",
+ "expressions",
+ "expression",
+ "or",
+ "terminal",
+ "group",
+ "star",
+ "symbol",
+ "literal",
+ "slash",
+ "dot" ]
+
+Racc_debug_parser = false
+
+##### State transition tables end #####
+
+# reduce 0 omitted
+
+def _reduce_1(val, _values, result)
+ result = Cat.new(val.first, val.last)
+ result
+end
+
+def _reduce_2(val, _values, result)
+ result = val.first
+ result
+end
+
+# reduce 3 omitted
+
+# reduce 4 omitted
+
+# reduce 5 omitted
+
+# reduce 6 omitted
+
+def _reduce_7(val, _values, result)
+ result = Group.new(val[1])
+ result
+end
+
+def _reduce_8(val, _values, result)
+ result = Or.new([val.first, val.last])
+ result
+end
+
+def _reduce_9(val, _values, result)
+ result = Star.new(Symbol.new(val.last))
+ result
+end
+
+# reduce 10 omitted
+
+# reduce 11 omitted
+
+# reduce 12 omitted
+
+# reduce 13 omitted
+
+def _reduce_14(val, _values, result)
+ result = Slash.new('/')
+ result
+end
+
+def _reduce_15(val, _values, result)
+ result = Symbol.new(val.first)
+ result
+end
+
+def _reduce_16(val, _values, result)
+ result = Literal.new(val.first)
+ result
+end
+
+def _reduce_17(val, _values, result)
+ result = Dot.new(val.first)
+ result
+end
+
+def _reduce_none(val, _values, result)
+ val[0]
+end
+
+ end # class Parser
+ end # module Journey
+ end # module ActionDispatch
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
new file mode 100644
index 0000000000..a2e1afed32
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -0,0 +1,47 @@
+class ActionDispatch::Journey::Parser
+
+token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
+
+rule
+ expressions
+ : expressions expression { result = Cat.new(val.first, val.last) }
+ | expression { result = val.first }
+ | or
+ ;
+ expression
+ : terminal
+ | group
+ | star
+ ;
+ group
+ : LPAREN expressions RPAREN { result = Group.new(val[1]) }
+ ;
+ or
+ : expressions OR expression { result = Or.new([val.first, val.last]) }
+ ;
+ star
+ : STAR { result = Star.new(Symbol.new(val.last)) }
+ ;
+ terminal
+ : symbol
+ | literal
+ | slash
+ | dot
+ ;
+ slash
+ : SLASH { result = Slash.new('/') }
+ ;
+ symbol
+ : SYMBOL { result = Symbol.new(val.first) }
+ ;
+ literal
+ : LITERAL { result = Literal.new(val.first) }
+ dot
+ : DOT { result = Dot.new(val.first) }
+ ;
+
+end
+
+---- header
+
+require 'action_dispatch/journey/parser_extras'
diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb
new file mode 100644
index 0000000000..14892f4321
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb
@@ -0,0 +1,23 @@
+require 'action_dispatch/journey/scanner'
+require 'action_dispatch/journey/nodes/node'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Parser < Racc::Parser # :nodoc:
+ include Journey::Nodes
+
+ def initialize
+ @scanner = Scanner.new
+ end
+
+ def parse(string)
+ @scanner.scan_setup(string)
+ do_parse
+ end
+
+ def next_token
+ @scanner.next_token
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
new file mode 100644
index 0000000000..4a571ec546
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -0,0 +1,196 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ module Path # :nodoc:
+ class Pattern # :nodoc:
+ attr_reader :spec, :requirements, :anchored
+
+ def initialize(strexp)
+ parser = Journey::Parser.new
+
+ @anchored = true
+
+ case strexp
+ when String
+ @spec = parser.parse(strexp)
+ @requirements = {}
+ @separators = "/.?"
+ when Router::Strexp
+ @spec = parser.parse(strexp.path)
+ @requirements = strexp.requirements
+ @separators = strexp.separators.join
+ @anchored = strexp.anchor
+ else
+ raise "wtf bro: #{strexp}"
+ end
+
+ @names = nil
+ @optional_names = nil
+ @required_names = nil
+ @re = nil
+ @offsets = nil
+ end
+
+ def ast
+ @spec.grep(Nodes::Symbol).each do |node|
+ re = @requirements[node.to_sym]
+ node.regexp = re if re
+ end
+
+ @spec.grep(Nodes::Star).each do |node|
+ node = node.left
+ node.regexp = @requirements[node.to_sym] || /(.+)/
+ end
+
+ @spec
+ end
+
+ def names
+ @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
+ end
+
+ def required_names
+ @required_names ||= names - optional_names
+ end
+
+ def optional_names
+ @optional_names ||= spec.grep(Nodes::Group).map { |group|
+ group.grep(Nodes::Symbol)
+ }.flatten.map { |n| n.name }.uniq
+ end
+
+ class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
+ attr_reader :offsets
+
+ def initialize(matchers)
+ @matchers = matchers
+ @capture_count = [0]
+ end
+
+ def visit(node)
+ super
+ @capture_count
+ end
+
+ def visit_SYMBOL(node)
+ node = node.to_sym
+
+ if @matchers.key?(node)
+ re = /#{@matchers[node]}|/
+ @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
+ else
+ @capture_count << (@capture_count.last || 0)
+ end
+ end
+ end
+
+ class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
+ def initialize(separator, matchers)
+ @separator = separator
+ @matchers = matchers
+ @separator_re = "([^#{separator}]+)"
+ super()
+ end
+
+ def accept(node)
+ %r{\A#{visit node}\Z}
+ end
+
+ def visit_CAT(node)
+ [visit(node.left), visit(node.right)].join
+ end
+
+ def visit_SYMBOL(node)
+ node = node.to_sym
+
+ return @separator_re unless @matchers.key?(node)
+
+ re = @matchers[node]
+ "(#{re})"
+ end
+
+ def visit_GROUP(node)
+ "(?:#{visit node.left})?"
+ end
+
+ def visit_LITERAL(node)
+ Regexp.escape(node.left)
+ end
+ alias :visit_DOT :visit_LITERAL
+
+ def visit_SLASH(node)
+ node.left
+ end
+
+ def visit_STAR(node)
+ re = @matchers[node.left.to_sym] || '.+'
+ "(#{re})"
+ end
+ end
+
+ class UnanchoredRegexp < AnchoredRegexp # :nodoc:
+ def accept(node)
+ %r{\A#{visit node}}
+ end
+ end
+
+ class MatchData # :nodoc:
+ attr_reader :names
+
+ def initialize(names, offsets, match)
+ @names = names
+ @offsets = offsets
+ @match = match
+ end
+
+ def captures
+ (length - 1).times.map { |i| self[i + 1] }
+ end
+
+ def [](x)
+ idx = @offsets[x - 1] + x
+ @match[idx]
+ end
+
+ def length
+ @offsets.length
+ end
+
+ def post_match
+ @match.post_match
+ end
+
+ def to_s
+ @match.to_s
+ end
+ end
+
+ def match(other)
+ return unless match = to_regexp.match(other)
+ MatchData.new(names, offsets, match)
+ end
+ alias :=~ :match
+
+ def source
+ to_regexp.source
+ end
+
+ def to_regexp
+ @re ||= regexp_visitor.new(@separators, @requirements).accept spec
+ end
+
+ private
+
+ def regexp_visitor
+ @anchored ? AnchoredRegexp : UnanchoredRegexp
+ end
+
+ def offsets
+ return @offsets if @offsets
+
+ viz = RegexpOffsets.new(@requirements)
+ @offsets = viz.accept(spec)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
new file mode 100644
index 0000000000..f8a53227f3
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -0,0 +1,94 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ class Route # :nodoc:
+ attr_reader :app, :path, :verb, :defaults, :ip, :name
+
+ attr_reader :constraints
+ alias :conditions :constraints
+
+ attr_accessor :precedence
+
+ ##
+ # +path+ is a path constraint.
+ # +constraints+ is a hash of constraints to be applied to this route.
+ def initialize(name, app, path, constraints, defaults = {})
+ constraints = constraints.dup
+ @name = name
+ @app = app
+ @path = path
+ @verb = constraints[:request_method] || //
+ @ip = constraints.delete(:ip) || //
+
+ @constraints = constraints
+ @constraints.keep_if { |_,v| Regexp === v || String === v }
+ @defaults = defaults
+ @required_defaults = nil
+ @required_parts = nil
+ @parts = nil
+ @decorated_ast = nil
+ @precedence = 0
+ end
+
+ def ast
+ @decorated_ast ||= begin
+ decorated_ast = path.ast
+ decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
+ decorated_ast
+ end
+ end
+
+ def requirements # :nodoc:
+ # needed for rails `rake routes`
+ path.requirements.merge(@defaults).delete_if { |_,v|
+ /.+?/ == v
+ }
+ end
+
+ def segments
+ @path.names
+ end
+
+ def required_keys
+ path.required_names.map { |x| x.to_sym } + required_defaults.keys
+ end
+
+ def score(constraints)
+ required_keys = path.required_names
+ supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
+
+ return -1 unless (required_keys - supplied_keys).empty?
+
+ score = (supplied_keys & path.names).length
+ score + (required_defaults.length * 2)
+ end
+
+ def parts
+ @parts ||= segments.map { |n| n.to_sym }
+ end
+ alias :segment_keys :parts
+
+ def format(path_options)
+ path_options.delete_if do |key, value|
+ value.to_s == defaults[key].to_s && !required_parts.include?(key)
+ end
+
+ Visitors::Formatter.new(path_options).accept(path.spec)
+ end
+
+ def optional_parts
+ path.optional_names.map { |n| n.to_sym }
+ end
+
+ def required_parts
+ @required_parts ||= path.required_names.map { |n| n.to_sym }
+ end
+
+ def required_defaults
+ @required_defaults ||= begin
+ matches = parts
+ @defaults.dup.delete_if { |k,_| matches.include?(k) }
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
new file mode 100644
index 0000000000..1fc45a2109
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -0,0 +1,168 @@
+require 'action_dispatch/journey/router/utils'
+require 'action_dispatch/journey/router/strexp'
+require 'action_dispatch/journey/routes'
+require 'action_dispatch/journey/formatter'
+
+before = $-w
+$-w = false
+require 'action_dispatch/journey/parser'
+$-w = before
+
+require 'action_dispatch/journey/route'
+require 'action_dispatch/journey/path/pattern'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Router # :nodoc:
+ class RoutingError < ::StandardError # :nodoc:
+ end
+
+ # :nodoc:
+ VERSION = '2.0.0'
+
+ class NullReq # :nodoc:
+ attr_reader :env
+ def initialize(env)
+ @env = env
+ end
+
+ def request_method
+ env['REQUEST_METHOD']
+ end
+
+ def path_info
+ env['PATH_INFO']
+ end
+
+ def ip
+ env['REMOTE_ADDR']
+ end
+
+ def [](k); env[k]; end
+ end
+
+ attr_reader :request_class, :formatter
+ attr_accessor :routes
+
+ def initialize(routes, options)
+ @options = options
+ @params_key = options[:parameters_key]
+ @request_class = options[:request_class] || NullReq
+ @routes = routes
+ end
+
+ def call(env)
+ env['PATH_INFO'] = Utils.normalize_path(env['PATH_INFO'])
+
+ find_routes(env).each do |match, parameters, route|
+ script_name, path_info, set_params = env.values_at('SCRIPT_NAME',
+ 'PATH_INFO',
+ @params_key)
+
+ unless route.path.anchored
+ env['SCRIPT_NAME'] = (script_name.to_s + match.to_s).chomp('/')
+ env['PATH_INFO'] = match.post_match
+ end
+
+ env[@params_key] = (set_params || {}).merge parameters
+
+ status, headers, body = route.app.call(env)
+
+ if 'pass' == headers['X-Cascade']
+ env['SCRIPT_NAME'] = script_name
+ env['PATH_INFO'] = path_info
+ env[@params_key] = set_params
+ next
+ end
+
+ return [status, headers, body]
+ end
+
+ return [404, {'X-Cascade' => 'pass'}, ['Not Found']]
+ end
+
+ def recognize(req)
+ find_routes(req.env).each do |match, parameters, route|
+ unless route.path.anchored
+ req.env['SCRIPT_NAME'] = match.to_s
+ req.env['PATH_INFO'] = match.post_match.sub(/^([^\/])/, '/\1')
+ end
+
+ yield(route, nil, parameters)
+ end
+ end
+
+ def visualizer
+ tt = GTG::Builder.new(ast).transition_table
+ groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
+ asts = groups.values.map { |v| v.first }
+ tt.visualizer(asts)
+ end
+
+ private
+
+ def partitioned_routes
+ routes.partitioned_routes
+ end
+
+ def ast
+ routes.ast
+ end
+
+ def simulator
+ routes.simulator
+ end
+
+ def custom_routes
+ partitioned_routes.last
+ end
+
+ def filter_routes(path)
+ return [] unless ast
+ data = simulator.match(path)
+ data ? data.memos : []
+ end
+
+ def find_routes env
+ req = request_class.new(env)
+
+ routes = filter_routes(req.path_info).concat custom_routes.find_all { |r|
+ r.path.match(req.path_info)
+ }
+ routes.concat get_routes_as_head(routes)
+
+ routes.sort_by!(&:precedence).select! { |r|
+ r.constraints.all? { |k, v| v === req.send(k) } &&
+ r.verb === req.request_method
+ }
+ routes.reject! { |r| req.ip && !(r.ip === req.ip) }
+
+ routes.map! { |r|
+ match_data = r.path.match(req.path_info)
+ match_names = match_data.names.map { |n| n.to_sym }
+ match_values = match_data.captures.map { |v| v && Utils.unescape_uri(v) }
+ info = Hash[match_names.zip(match_values).find_all { |_, y| y }]
+
+ [match_data, r.defaults.merge(info), r]
+ }
+ end
+
+ def get_routes_as_head(routes)
+ precedence = (routes.map(&:precedence).max || 0) + 1
+ routes = routes.select { |r|
+ r.verb === "GET" && !(r.verb === "HEAD")
+ }.map! { |r|
+ Route.new(r.name,
+ r.app,
+ r.path,
+ r.conditions.merge(request_method: "HEAD"),
+ r.defaults).tap do |route|
+ route.precedence = r.precedence + precedence
+ end
+ }
+ routes.flatten!
+ routes
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/router/strexp.rb b/actionpack/lib/action_dispatch/journey/router/strexp.rb
new file mode 100644
index 0000000000..f97f1a223e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/router/strexp.rb
@@ -0,0 +1,24 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ class Router # :nodoc:
+ class Strexp # :nodoc:
+ class << self
+ alias :compile :new
+ end
+
+ attr_reader :path, :requirements, :separators, :anchor
+
+ def initialize(path, requirements, separators, anchor = true)
+ @path = path
+ @requirements = requirements
+ @separators = separators
+ @anchor = anchor
+ end
+
+ def names
+ @path.scan(/:\w+/).map { |s| s.tr(':', '') }
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb
new file mode 100644
index 0000000000..462f1a122d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/router/utils.rb
@@ -0,0 +1,54 @@
+require 'uri'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Router # :nodoc:
+ class Utils # :nodoc:
+ # Normalizes URI path.
+ #
+ # Strips off trailing slash and ensures there is a leading slash.
+ #
+ # normalize_path("/foo") # => "/foo"
+ # normalize_path("/foo/") # => "/foo"
+ # normalize_path("foo") # => "/foo"
+ # normalize_path("") # => "/"
+ def self.normalize_path(path)
+ path = "/#{path}"
+ path.squeeze!('/')
+ path.sub!(%r{/+\Z}, '')
+ path = '/' if path == ''
+ path
+ end
+
+ # URI path and fragment escaping
+ # http://tools.ietf.org/html/rfc3986
+ module UriEscape # :nodoc:
+ # Symbol captures can generate multiple path segments, so include /.
+ reserved_segment = '/'
+ reserved_fragment = '/?'
+ reserved_pchar = ':@&=+$,;%'
+
+ safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
+ safe_segment = "#{safe_pchar}#{reserved_segment}"
+ safe_fragment = "#{safe_pchar}#{reserved_fragment}"
+ UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
+ UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
+ end
+
+ Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
+
+ def self.escape_path(path)
+ Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
+ end
+
+ def self.escape_fragment(fragment)
+ Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
+ end
+
+ def self.unescape_uri(uri)
+ Parser.unescape(uri)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb
new file mode 100644
index 0000000000..32829a1f20
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/routes.rb
@@ -0,0 +1,76 @@
+module ActionDispatch
+ module Journey # :nodoc:
+ # The Routing table. Contains all routes for a system. Routes can be
+ # added to the table by calling Routes#add_route.
+ class Routes # :nodoc:
+ include Enumerable
+
+ attr_reader :routes, :named_routes
+
+ def initialize
+ @routes = []
+ @named_routes = {}
+ @ast = nil
+ @partitioned_routes = nil
+ @simulator = nil
+ end
+
+ def length
+ @routes.length
+ end
+ alias :size :length
+
+ def last
+ @routes.last
+ end
+
+ def each(&block)
+ routes.each(&block)
+ end
+
+ def clear
+ routes.clear
+ end
+
+ def partitioned_routes
+ @partitioned_routes ||= routes.partition { |r|
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
+ }
+ end
+
+ def ast
+ return @ast if @ast
+ return if partitioned_routes.first.empty?
+
+ asts = partitioned_routes.first.map { |r| r.ast }
+ @ast = Nodes::Or.new(asts)
+ end
+
+ def simulator
+ return @simulator if @simulator
+
+ gtg = GTG::Builder.new(ast).transition_table
+ @simulator = GTG::Simulator.new(gtg)
+ end
+
+ # Add a route to the routing table.
+ def add_route(app, path, conditions, defaults, name = nil)
+ route = Route.new(name, app, path, conditions, defaults)
+
+ route.precedence = routes.length
+ routes << route
+ named_routes[name] = route if name && !named_routes[name]
+ clear_cache!
+ route
+ end
+
+ private
+
+ def clear_cache!
+ @ast = nil
+ @partitioned_routes = nil
+ @simulator = nil
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb
new file mode 100644
index 0000000000..633be11a2d
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/scanner.rb
@@ -0,0 +1,61 @@
+require 'strscan'
+
+module ActionDispatch
+ module Journey # :nodoc:
+ class Scanner # :nodoc:
+ def initialize
+ @ss = nil
+ end
+
+ def scan_setup(str)
+ @ss = StringScanner.new(str)
+ end
+
+ def eos?
+ @ss.eos?
+ end
+
+ def pos
+ @ss.pos
+ end
+
+ def pre_match
+ @ss.pre_match
+ end
+
+ def next_token
+ return if @ss.eos?
+
+ until token = scan || @ss.eos?; end
+ token
+ end
+
+ private
+
+ def scan
+ case
+ # /
+ when text = @ss.scan(/\//)
+ [:SLASH, text]
+ when text = @ss.scan(/\*\w+/)
+ [:STAR, text]
+ when text = @ss.scan(/\(/)
+ [:LPAREN, text]
+ when text = @ss.scan(/\)/)
+ [:RPAREN, text]
+ when text = @ss.scan(/\|/)
+ [:OR, text]
+ when text = @ss.scan(/\./)
+ [:DOT, text]
+ when text = @ss.scan(/:\w+/)
+ [:SYMBOL, text]
+ when text = @ss.scan(/[\w%\-~]+/)
+ [:LITERAL, text]
+ # any char
+ when text = @ss.scan(/./)
+ [:LITERAL, text]
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
new file mode 100644
index 0000000000..46bd58c178
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -0,0 +1,189 @@
+# encoding: utf-8
+module ActionDispatch
+ module Journey # :nodoc:
+ module Visitors # :nodoc:
+ class Visitor # :nodoc:
+ DISPATCH_CACHE = Hash.new { |h,k|
+ h[k] = "visit_#{k}"
+ }
+
+ def accept(node)
+ visit(node)
+ end
+
+ private
+
+ def visit node
+ send(DISPATCH_CACHE[node.type], node)
+ end
+
+ def binary(node)
+ visit(node.left)
+ visit(node.right)
+ end
+ def visit_CAT(n); binary(n); end
+
+ def nary(node)
+ node.children.each { |c| visit(c) }
+ end
+ def visit_OR(n); nary(n); end
+
+ def unary(node)
+ visit(node.left)
+ end
+ def visit_GROUP(n); unary(n); end
+ def visit_STAR(n); unary(n); end
+
+ def terminal(node); end
+ %w{ LITERAL SYMBOL SLASH DOT }.each do |t|
+ class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
+ end
+ end
+
+ # Loop through the requirements AST
+ class Each < Visitor # :nodoc:
+ attr_reader :block
+
+ def initialize(block)
+ @block = block
+ end
+
+ def visit(node)
+ super
+ block.call(node)
+ end
+ end
+
+ class String < Visitor # :nodoc:
+ private
+
+ def binary(node)
+ [visit(node.left), visit(node.right)].join
+ end
+
+ def nary(node)
+ node.children.map { |c| visit(c) }.join '|'
+ end
+
+ def terminal(node)
+ node.left
+ end
+
+ def visit_GROUP(node)
+ "(#{visit(node.left)})"
+ end
+ end
+
+ # Used for formatting urls (url_for)
+ class Formatter < Visitor # :nodoc:
+ attr_reader :options, :consumed
+
+ def initialize(options)
+ @options = options
+ @consumed = {}
+ end
+
+ private
+
+ def visit_GROUP(node)
+ if consumed == options
+ nil
+ else
+ route = visit(node.left)
+ route.include?("\0") ? nil : route
+ end
+ end
+
+ def terminal(node)
+ node.left
+ end
+
+ def binary(node)
+ [visit(node.left), visit(node.right)].join
+ end
+
+ def nary(node)
+ node.children.map { |c| visit(c) }.join
+ end
+
+ def visit_SYMBOL(node)
+ key = node.to_sym
+
+ if value = options[key]
+ consumed[key] = value
+ Router::Utils.escape_path(value)
+ else
+ "\0"
+ end
+ end
+ end
+
+ class Dot < Visitor # :nodoc:
+ def initialize
+ @nodes = []
+ @edges = []
+ end
+
+ def accept(node)
+ super
+ <<-eodot
+ digraph parse_tree {
+ size="8,5"
+ node [shape = none];
+ edge [dir = none];
+ #{@nodes.join "\n"}
+ #{@edges.join("\n")}
+ }
+ eodot
+ end
+
+ private
+
+ def binary(node)
+ node.children.each do |c|
+ @edges << "#{node.object_id} -> #{c.object_id};"
+ end
+ super
+ end
+
+ def nary(node)
+ node.children.each do |c|
+ @edges << "#{node.object_id} -> #{c.object_id};"
+ end
+ super
+ end
+
+ def unary(node)
+ @edges << "#{node.object_id} -> #{node.left.object_id};"
+ super
+ end
+
+ def visit_GROUP(node)
+ @nodes << "#{node.object_id} [label=\"()\"];"
+ super
+ end
+
+ def visit_CAT(node)
+ @nodes << "#{node.object_id} [label=\"○\"];"
+ super
+ end
+
+ def visit_STAR(node)
+ @nodes << "#{node.object_id} [label=\"*\"];"
+ super
+ end
+
+ def visit_OR(node)
+ @nodes << "#{node.object_id} [label=\"|\"];"
+ super
+ end
+
+ def terminal(node)
+ value = node.left
+
+ @nodes << "#{node.object_id} [label=\"#{value}\"];"
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/fsm.css b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
new file mode 100644
index 0000000000..50caebaa18
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visualizer/fsm.css
@@ -0,0 +1,34 @@
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, Sans-Serif;
+ margin: 0;
+}
+
+h1 {
+ font-size: 2.0em; font-weight: bold; text-align: center;
+ color: white; background-color: black;
+ padding: 5px 0;
+ margin: 0 0 20px;
+}
+
+h2 {
+ text-align: center;
+ display: none;
+ font-size: 0.5em;
+}
+
+div#chart-2 {
+ height: 350px;
+}
+
+.clearfix {display: inline-block; }
+.input { overflow: show;}
+.instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em}
+.instruction p { padding: 0 0 5px; }
+.instruction li { padding: 0 10px 5px; }
+
+.form { background: #EEE; padding: 20px 30px; border-radius: 5px; margin-left: auto; margin-right: auto; width: 500px; margin-bottom: 20px}
+.form p, .form form { text-align: center }
+.form form {padding: 0 10px 5px; }
+.form .fun_routes { font-size: 0.9em;}
+.form .fun_routes a { margin: 0 5px 0 0; }
+
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/fsm.js b/actionpack/lib/action_dispatch/journey/visualizer/fsm.js
new file mode 100644
index 0000000000..d9bcaef928
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visualizer/fsm.js
@@ -0,0 +1,134 @@
+function tokenize(input, callback) {
+ while(input.length > 0) {
+ callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]);
+ input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, '');
+ }
+}
+
+var graph = d3.select("#chart-2 svg");
+var svg_edges = {};
+var svg_nodes = {};
+
+graph.selectAll("g.edge").each(function() {
+ var node = d3.select(this);
+ var index = node.select("title").text().split("->");
+ var left = parseInt(index[0]);
+ var right = parseInt(index[1]);
+
+ if(!svg_edges[left]) { svg_edges[left] = {} }
+ svg_edges[left][right] = node;
+});
+
+graph.selectAll("g.node").each(function() {
+ var node = d3.select(this);
+ var index = parseInt(node.select("title").text());
+ svg_nodes[index] = node;
+});
+
+function reset_graph() {
+ for(var key in svg_edges) {
+ for(var mkey in svg_edges[key]) {
+ var node = svg_edges[key][mkey];
+ var path = node.select("path");
+ var arrow = node.select("polygon");
+ path.style("stroke", "black");
+ arrow.style("stroke", "black").style("fill", "black");
+ }
+ }
+
+ for(var key in svg_nodes) {
+ var node = svg_nodes[key];
+ node.select('ellipse').style("fill", "white");
+ node.select('polygon').style("fill", "white");
+ }
+ return false;
+}
+
+function highlight_edge(from, to) {
+ var node = svg_edges[from][to];
+ var path = node.select("path");
+ var arrow = node.select("polygon");
+
+ path
+ .transition().duration(500)
+ .style("stroke", "green");
+
+ arrow
+ .transition().duration(500)
+ .style("stroke", "green").style("fill", "green");
+}
+
+function highlight_state(index, color) {
+ if(!color) { color = "green"; }
+
+ svg_nodes[index].select('ellipse')
+ .style("fill", "white")
+ .transition().duration(500)
+ .style("fill", color);
+}
+
+function highlight_finish(index) {
+ svg_nodes[index].select('polygon')
+ .style("fill", "while")
+ .transition().duration(500)
+ .style("fill", "blue");
+}
+
+function match(input) {
+ reset_graph();
+ var table = tt();
+ var states = [0];
+ var regexp_states = table['regexp_states'];
+ var string_states = table['string_states'];
+ var accepting = table['accepting'];
+
+ highlight_state(0);
+
+ tokenize(input, function(token) {
+ var new_states = [];
+ for(var key in states) {
+ var state = states[key];
+
+ if(string_states[state] && string_states[state][token]) {
+ var new_state = string_states[state][token];
+ highlight_edge(state, new_state);
+ highlight_state(new_state);
+ new_states.push(new_state);
+ }
+
+ if(regexp_states[state]) {
+ for(var key in regexp_states[state]) {
+ var re = new RegExp("^" + key + "$");
+ if(re.test(token)) {
+ var new_state = regexp_states[state][key];
+ highlight_edge(state, new_state);
+ highlight_state(new_state);
+ new_states.push(new_state);
+ }
+ }
+ }
+ }
+
+ if(new_states.length == 0) {
+ return;
+ }
+ states = new_states;
+ });
+
+ for(var key in states) {
+ var state = states[key];
+ if(accepting[state]) {
+ for(var mkey in svg_edges[state]) {
+ if(!regexp_states[mkey] && !string_states[mkey]) {
+ highlight_edge(state, mkey);
+ highlight_finish(mkey);
+ }
+ }
+ } else {
+ highlight_state(state, "red");
+ }
+ }
+
+ return false;
+}
+
diff --git a/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb
new file mode 100644
index 0000000000..6aff10956a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/journey/visualizer/index.html.erb
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title><%= title %></title>
+ <link rel="stylesheet" href="https://raw.github.com/gist/1706081/af944401f75ea20515a02ddb3fb43d23ecb8c662/reset.css" type="text/css">
+ <style>
+ <% stylesheets.each do |style| %>
+ <%= style %>
+ <% end %>
+ </style>
+ <script src="https://raw.github.com/gist/1706081/df464722a05c3c2bec450b7b5c8240d9c31fa52d/d3.min.js" type="text/javascript"></script>
+ </head>
+ <body>
+ <div id="wrapper">
+ <h1>Routes FSM with NFA simulation</h1>
+ <div class="instruction form">
+ <p>
+ Type a route in to the box and click "simulate".
+ </p>
+ <form onsubmit="return match(this.route.value);">
+ <input type="text" size="30" name="route" value="/articles/new" />
+ <button>simulate</button>
+ <input type="reset" value="reset" onclick="return reset_graph();"/>
+ </form>
+ <p class="fun_routes">
+ Some fun routes to try:
+ <% fun_routes.each do |path| %>
+ <a href="#" onclick="document.forms[0].elements[0].value=this.text.replace(/^\s+|\s+$/g,''); return match(this.text.replace(/^\s+|\s+$/g,''));">
+ <%= path %>
+ </a>
+ <% end %>
+ </p>
+ </div>
+ <div class='chart' id='chart-2'>
+ <%= svg %>
+ </div>
+ <div class="instruction">
+ <p>
+ This is a FSM for a system that has the following routes:
+ </p>
+ <ul>
+ <% paths.each do |route| %>
+ <li><%= route %></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
+ <% javascripts.each do |js| %>
+ <script><%= js %></script>
+ <% end %>
+ </body>
+</html>
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 2f148752cb..121a11c8e1 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -87,6 +87,9 @@ module ActionDispatch
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
TOKEN_KEY = "action_dispatch.secret_token".freeze
+ # Cookies can typically store 4096 bytes.
+ MAX_COOKIE_SIZE = 4096
+
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
@@ -293,13 +296,17 @@ module ActionDispatch
end
end
- class PermanentCookieJar < CookieJar #:nodoc:
+ class PermanentCookieJar #:nodoc:
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@key_generator = key_generator
@options = options
end
+ def [](key)
+ @parent_jar[name.to_s]
+ end
+
def []=(key, options)
if options.is_a?(Hash)
options.symbolize_keys!
@@ -311,14 +318,25 @@ module ActionDispatch
@parent_jar[key] = options
end
+ def permanent
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ end
+
+ def signed
+ @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ end
+
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+
def method_missing(method, *arguments, &block)
- @parent_jar.send(method, *arguments, &block)
+ ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
+ "You probably want to try this method over the parent CookieJar."
end
end
- class SignedCookieJar < CookieJar #:nodoc:
- MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
-
+ class SignedCookieJar #:nodoc:
def initialize(parent_jar, key_generator, options = {})
@parent_jar = parent_jar
@options = options
@@ -346,12 +364,25 @@ module ActionDispatch
@parent_jar[key] = options
end
+ def permanent
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ end
+
+ def signed
+ @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ end
+
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+
def method_missing(method, *arguments, &block)
- @parent_jar.send(method, *arguments, &block)
+ ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
+ "You probably want to try this method over the parent CookieJar."
end
end
- class EncryptedCookieJar < SignedCookieJar #:nodoc:
+ class EncryptedCookieJar #:nodoc:
def initialize(parent_jar, key_generator, options = {})
if ActiveSupport::DummyKeyGenerator === key_generator
raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." +
@@ -365,8 +396,8 @@ module ActionDispatch
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
end
- def [](name)
- if encrypted_message = @parent_jar[name]
+ def [](key)
+ if encrypted_message = @parent_jar[key]
@encryptor.decrypt_and_verify(encrypted_message)
end
rescue ActiveSupport::MessageVerifier::InvalidSignature,
@@ -385,6 +416,23 @@ module ActionDispatch
raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
@parent_jar[key] = options
end
+
+ def permanent
+ @permanent ||= PermanentCookieJar.new(self, @key_generator, @options)
+ end
+
+ def signed
+ @signed ||= SignedCookieJar.new(self, @key_generator, @options)
+ end
+
+ def encrypted
+ @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options)
+ end
+
+ def method_missing(method, *arguments, &block)
+ ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " +
+ "You probably want to try this method over the parent CookieJar."
+ end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 0f0589a844..6bc5876b6c 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -2,12 +2,11 @@ require 'action_dispatch/http/request'
require 'action_dispatch/middleware/exception_wrapper'
require 'action_dispatch/routing/inspector'
-
module ActionDispatch
# This middleware is responsible for logging exceptions and
# showing a debugging page in case the request is local.
class DebugExceptions
- RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
+ RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
def initialize(app, routes_app = nil)
@app = app
@@ -16,10 +15,9 @@ module ActionDispatch
def call(env)
begin
- response = @app.call(env)
+ response = (_, headers, body = @app.call(env))
- if response[1]['X-Cascade'] == 'pass'
- body = response[2]
+ if headers['X-Cascade'] == 'pass'
body.close if body.respond_to?(:close)
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
end
@@ -43,9 +41,11 @@ module ActionDispatch
:application_trace => wrapper.application_trace,
:framework_trace => wrapper.framework_trace,
:full_trace => wrapper.full_trace,
- :routes => formatted_routes(exception)
+ :routes_inspector => routes_inspector(exception),
+ :source_extract => wrapper.source_extract,
+ :line_number => wrapper.line_number,
+ :file => wrapper.file
)
-
file = "rescues/#{wrapper.rescue_template}"
body = template.render(:template => file, :layout => 'rescues/layout')
render(wrapper.status_code, body)
@@ -83,11 +83,10 @@ module ActionDispatch
@stderr_logger ||= ActiveSupport::Logger.new($stderr)
end
- def formatted_routes(exception)
+ def routes_inspector(exception)
return false unless @routes_app.respond_to?(:routes)
if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)
- inspector = ActionDispatch::Routing::RoutesInspector.new
- inspector.format(@routes_app.routes.routes).join("\n")
+ ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index ae38c56a67..869d0aa7af 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,5 +1,4 @@
require 'action_controller/metal/exceptions'
-require 'active_support/core_ext/exception'
require 'active_support/core_ext/class/attribute_accessors'
module ActionDispatch
@@ -25,7 +24,7 @@ module ActionDispatch
'ActionView::Template::Error' => 'template_error'
)
- attr_reader :env, :exception
+ attr_reader :env, :exception, :line_number, :file
def initialize(env, exception)
@env = env
@@ -56,6 +55,15 @@ module ActionDispatch
Rack::Utils.status_code(@@rescue_responses[class_name])
end
+ def source_extract
+ if application_trace && trace = application_trace.first
+ file, line, _ = trace.split(":")
+ @file = file
+ @line_number = line.to_i
+ source_fragment(@file, @line_number)
+ end
+ end
+
private
def original_exception(exception)
@@ -81,5 +89,17 @@ module ActionDispatch
def backtrace_cleaner
@backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
end
+
+ def source_fragment(path, line)
+ return unless Rails.respond_to?(:root) && Rails.root
+ full_path = Rails.root.join(path)
+ if File.exists?(full_path)
+ File.open(full_path, "r") do |file|
+ start = [line - 3, 0].max
+ lines = file.each_line.drop(start).take(6)
+ Hash[*(start+1..(lines.count+start)).zip(lines).flatten]
+ end
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 9928b7cc3a..f24e9b8e18 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -4,7 +4,7 @@ module ActionDispatch
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash
- @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep)
+ @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
end
end
@@ -70,16 +70,30 @@ module ActionDispatch
end
end
- # Implementation detail: please do not change the signature of the
- # FlashHash class. Doing that will likely affect all Rails apps in
- # production as the FlashHash currently stored in their sessions will
- # become invalid.
class FlashHash
include Enumerable
- def initialize #:nodoc:
- @discard = Set.new
- @flashes = {}
+ def self.from_session_value(value)
+ flash = case value
+ when FlashHash # Rails 3.1, 3.2
+ new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
+ when Hash # Rails 4.0
+ new(value['flashes'], value['discard'])
+ else
+ new
+ end
+
+ flash.tap(&:sweep)
+ end
+
+ def to_session_value
+ return nil if empty?
+ {'discard' => @discard.to_a, 'flashes' => @flashes}
+ end
+
+ def initialize(flashes = {}, discard = []) #:nodoc:
+ @discard = Set.new(discard)
+ @flashes = flashes
@now = nil
end
@@ -91,7 +105,7 @@ module ActionDispatch
super
end
- def []=(k, v) #:nodoc:
+ def []=(k, v)
@discard.delete k
@flashes[k] = v
end
@@ -223,7 +237,7 @@ module ActionDispatch
if flash_hash
if !flash_hash.empty? || session.key?('flash')
- session["flash"] = flash_hash
+ session["flash"] = flash_hash.to_session_value
new_hash = flash_hash.dup
else
new_hash = flash_hash
@@ -233,7 +247,7 @@ module ActionDispatch
end
if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
- session.key?('flash') && session['flash'].empty?
+ session.key?('flash') && session['flash'].nil?
session.delete('flash')
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 5abf8f2802..4e36c9bb49 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -1,23 +1,59 @@
module ActionDispatch
+ # This middleware calculates the IP address of the remote client that is
+ # making the request. It does this by checking various headers that could
+ # contain the address, and then picking the last-set address that is not
+ # on the list of trusted IPs. This follows the precendent set by e.g.
+ # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
+ # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
+ # by @gingerlime. A more detailed explanation of the algorithm is given
+ # at GetIp#calculate_ip.
+ #
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
+ # requires. Some Rack servers simply drop preceeding headers, and only report
+ # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
+ # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
+ # then you should test your Rack server to make sure your data is good.
+ #
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
+ # This middleware assumes that there is at least one proxy sitting around
+ # and setting headers with the client's remote IP address. If you don't use
+ # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
+ # claim to have any IP address by setting the X-Forwarded-For header. If you
+ # care about that, then you need to explicitly drop or ignore those headers
+ # sometime before this middleware runs.
class RemoteIp
- class IpSpoofAttackError < StandardError ; end
+ class IpSpoofAttackError < StandardError; end
- # IP addresses that are "trusted proxies" that can be stripped from
- # the comma-delimited list in the X-Forwarded-For header. See also:
- # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
- # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
+ # The default trusted IPs list simply includes IP addresses that are
+ # guaranteed by the IP specification to be private addresses. Those will
+ # not be the ultimate client IP in production, and so are discarded. See
+ # http://en.wikipedia.org/wiki/Private_network for details.
TRUSTED_PROXIES = %r{
- ^127\.0\.0\.1$ | # localhost
- ^::1$ |
- ^(10 | # private IP 10.x.x.x
- 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
- 192\.168 | # private IP 192.168.x.x
- fc00:: # private IP fc00
- )\.
+ ^127\.0\.0\.1$ | # localhost IPv4
+ ^::1$ | # localhost IPv6
+ ^fc00: | # private IPv6 range fc00
+ ^10\. | # private IPv4 range 10.x.x.x
+ ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255
+ ^192\.168\. # private IPv4 range 192.168.x.x
}x
attr_reader :check_ip, :proxies
+ # Create a new +RemoteIp+ middleware instance.
+ #
+ # The +check_ip_spoofing+ option is on by default. When on, an exception
+ # is raised if it looks like the client is trying to lie about its own IP
+ # address. It makes sense to turn off this check on sites aimed at non-IP
+ # clients (like WAP devices), or behind proxies that set headers in an
+ # incorrect or confusing way (like AWS ELB).
+ #
+ # The +custom_trusted+ argument can take a regex, which will be used
+ # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
+ # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
+ # middle (or at the beginning) of the X-Forwarded-For list, with your proxy
+ # servers after it. If your proxies aren't removed, pass them in via the
+ # +custom_trusted+ parameter. That way, the middleware will ignore those
+ # IP addresses, and return the one that you want.
def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
@app = app
@check_ip = check_ip_spoofing
@@ -31,15 +67,23 @@ module ActionDispatch
end
end
+ # Since the IP address may not be needed, we store the object here
+ # without calculating the IP to keep from slowing down the majority of
+ # requests. For those requests that do need to know the IP, the
+ # GetIp#calculate_ip method will calculate the memoized client IP address.
def call(env)
env["action_dispatch.remote_ip"] = GetIp.new(env, self)
@app.call(env)
end
+ # The GetIp class exists as a way to defer processing of the request data
+ # into an actual IP address. If the ActionDispatch::Request#remote_ip method
+ # is called, this class will calculate the value and then memoize it.
class GetIp
- # IP v4 and v6 (with compression) validation regexp
- # https://gist.github.com/1289635
+ # This constant contains a regular expression that validates every known
+ # form of IP v4 and v6 address, with or without abbreviations, adapted
+ # from {this gist}[https://gist.github.com/1289635].
VALID_IP = %r{
(^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
(^(
@@ -63,62 +107,78 @@ module ActionDispatch
}x
def initialize(env, middleware)
- @env = env
- @middleware = middleware
- @ip = nil
+ @env = env
+ @check_ip = middleware.check_ip
+ @proxies = middleware.proxies
end
- # Determines originating IP address. REMOTE_ADDR is the standard
- # but will be wrong if the user is behind a proxy. Proxies will set
- # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
- # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
- # multiple chained proxies. The first address which is in this list
- # if it's not a known proxy will be the originating IP.
- # Format of HTTP_X_FORWARDED_FOR:
- # client_ip, proxy_ip1, proxy_ip2...
- # http://en.wikipedia.org/wiki/X-Forwarded-For
+ # Sort through the various IP address headers, looking for the IP most
+ # likely to be the address of the actual remote client making this
+ # request.
+ #
+ # REMOTE_ADDR will be correct if the request is made directly against the
+ # Ruby process, on e.g. Heroku. When the request is proxied by another
+ # server like HAProxy or Nginx, the IP address that made the original
+ # request will be put in an X-Forwarded-For header. If there are multiple
+ # proxies, that header may contain a list of IPs. Other proxy services
+ # set the Client-Ip header instead, so we check that too.
+ #
+ # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
+ # while the first IP in the list is likely to be the "originating" IP,
+ # it could also have been set by the client maliciously.
+ #
+ # In order to find the first address that is (probably) accurate, we
+ # take the list of IPs, remove known and trusted proxies, and then take
+ # the last address left, which was presumably set by one of those proxies.
def calculate_ip
- client_ip = @env['HTTP_CLIENT_IP']
- forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first
- remote_addrs = ips_from('REMOTE_ADDR')
-
- check_ip = client_ip && @middleware.check_ip
- if check_ip && forwarded_ip != client_ip
+ # Set by the Rack web server, this is a single value.
+ remote_addr = ips_from('REMOTE_ADDR').last
+
+ # Could be a CSV list and/or repeated headers that were concatenated.
+ client_ips = ips_from('HTTP_CLIENT_IP').reverse
+ forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse
+
+ # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
+ # If they are both set, it means that this request passed through two
+ # proxies with incompatible IP header conventions, and there is no way
+ # for us to determine which header is the right one after the fact.
+ # Since we have no idea, we give up and explode.
+ should_check_ip = @check_ip && client_ips.last
+ if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
- raise IpSpoofAttackError, "IP spoofing attack?!" \
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
+ raise IpSpoofAttackError, "IP spoofing attack?! " +
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " +
"HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
end
- client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten
- if client_ips.present?
- client_ips.first
- else
- # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR
- remote_addrs.find { |ip| valid_ip? ip }
- end
+ # We assume these things about the IP headers:
+ #
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
+ # - Client-Ip is propagated from the outermost proxy, or is blank
+ # - REMOTE_ADDR will be the IP that made the request to Rack
+ ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
+
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
+ filter_proxies(ips).first || remote_addr
end
+ # Memoizes the value returned by #calculate_ip and returns it for
+ # ActionDispatch::Request to use.
def to_s
@ip ||= calculate_ip
end
- private
+ protected
def ips_from(header)
- @env[header] ? @env[header].strip.split(/[,\s]+/) : []
- end
-
- def valid_ip?(ip)
- ip =~ VALID_IP
- end
-
- def not_a_proxy?(ip)
- ip !~ @middleware.proxies
+ # Split the comma-separated list into an array of strings
+ ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
+ # Only return IPs that are valid according to the regex
+ ips.select{ |ip| ip =~ VALID_IP }
end
- def remove_proxies(ips)
- ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
+ def filter_proxies(ips)
+ ips.reject { |ip| ip =~ @proxies }
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 2106a09fd4..4437b50f1f 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -21,15 +21,12 @@ module ActionDispatch
#
# Session options:
#
- # * <tt>:secret</tt>: An application-wide key string or block returning a
- # string called per generated digest. The block is called with the
- # CGI::Session instance as an argument. It's important that the secret
- # is not vulnerable to a dictionary attack. Therefore, you should choose
- # a secret consisting of random numbers and letters and more than 30
- # characters.
+ # * <tt>:secret</tt>: An application-wide key string. It's important that
+ # the secret is not vulnerable to a dictionary attack. Therefore, you
+ # should choose a secret consisting of random numbers and letters and
+ # more than 30 characters.
#
# secret: '449fe2e7daee471bffae2fd8dc02313d'
- # secret: Proc.new { User.current_user.secret_key }
#
# * <tt>:digest</tt>: The message digest algorithm used to verify session
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
@@ -95,22 +92,17 @@ module ActionDispatch
end
# This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
- #
- # To use this CookieStore use this
+ # To use this CookieStore set
#
# Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
#
# in your config/initializers/session_store.rb
#
- # You will also need to go to your config/initializers/secret_token.rb
- #
- # leave what you already had in your 3.2.x app
- #
- # Myapp::Application.config.secret_token = 'some secret'
- #
- # and also set secret_key_base to allow Rails to upgrade your users cookies
+ # You will also need to add
#
# Myapp::Application.config.secret_key_base = 'some secret'
+ #
+ # in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
private
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 9098f4e170..9e03cbf2b7 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -45,7 +45,7 @@ module ActionDispatch
# http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
def hsts_headers
if @hsts
- value = "max-age=#{@hsts[:expires]}"
+ value = "max-age=#{@hsts[:expires].to_i}"
value += "; includeSubDomains" if @hsts[:subdomains]
{ 'Strict-Transport-Security' => value }
else
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 823f5d25b6..ab24118f3e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -1,8 +1,8 @@
<% unless @exception.blamed_files.blank? %>
<% if (hide = @exception.blamed_files.length > 8) %>
- <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a>
+ <a href="#" onclick="toggleTrace()">Toggle blamed files</a>
<% end %>
- <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
+ <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
<% end %>
<%
@@ -18,14 +18,17 @@
%>
<h2 style="margin-top: 30px">Request</h2>
-<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
+<p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
-<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
-<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
-
-<p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p>
-<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
+<div class="details">
+ <div class="summary"><a href="#" onclick="toggleSessionDump()">Toggle session dump</a></div>
+ <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div>
+</div>
+<div class="details">
+ <div class="summary"><a href="#" onclick="toggleEnvDump()">Toggle env dump</a></div>
+ <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div>
+</div>
<h2 style="margin-top: 30px">Response</h2>
-<p><b>Headers</b>: <pre><%=h defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
+<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
new file mode 100644
index 0000000000..38429cb78e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb
@@ -0,0 +1,25 @@
+<% if @source_extract %>
+<div class="source">
+<div class="info">
+ Extracted source (around line <strong>#<%= @line_number %></strong>):
+</div>
+<div class="data">
+ <table cellpadding="0" cellspacing="0" class="lines">
+ <tr>
+ <td>
+ <pre class="line_numbers">
+ <% @source_extract.keys.each do |line_number| %>
+<span><%= line_number -%></span>
+ <% end %>
+ </pre>
+ </td>
+<td width="100%">
+<pre>
+<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @line_number -%>"><%= source -%></div><% end -%>
+</pre>
+</td>
+ </tr>
+ </table>
+</div>
+</div>
+<% end %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
index 8771b5fd6d..9d947aea40 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb
@@ -12,15 +12,15 @@
<div id="traces">
<% names.each do |name| %>
<%
- show = "document.getElementById('#{name.gsub(/\s/, '-')}').style.display='block';"
- hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub(/\s/, '-')}').style.display='none';"}
+ show = "show('#{name.gsub(/\s/, '-')}');"
+ hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"}
%>
<a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
<% end %>
<% traces.each do |name, trace| %>
<div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;">
- <pre><code><%=h trace.join "\n" %></code></pre>
+ <pre><code><%= trace.join "\n" %></code></pre>
</div>
<% end %>
</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index c5043c5e7b..57a2940802 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -1,10 +1,16 @@
-<h1>
- <%=h @exception.class.to_s %>
- <% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
- <% end %>
-</h1>
-<pre><%=h @exception.message %></pre>
+<header>
+ <h1>
+ <%= @exception.class.to_s %>
+ <% if @request.parameters['controller'] %>
+ in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+ <% end %>
+ </h1>
+</header>
-<%= render template: "rescues/_trace" %>
-<%= render template: "rescues/_request_and_response" %>
+<div id="container">
+ <h2><%= @exception.message %></h2>
+
+ <%= render template: "rescues/_source" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index 1a308707d1..9878c2747e 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -4,7 +4,11 @@
<meta charset="utf-8" />
<title>Action Controller: Exception caught</title>
<style>
- body { background-color: #fff; color: #333; }
+ body {
+ background-color: #FAFAFA;
+ color: #333;
+ margin: 0px;
+ }
body, p, ol, ul, td {
font-family: helvetica, verdana, arial, sans-serif;
@@ -13,16 +17,127 @@
}
pre {
- background-color: #eee;
- padding: 10px;
font-size: 11px;
white-space: pre-wrap;
}
- a { color: #000; }
+ pre.box {
+ border: 1px solid #EEE;
+ padding: 10px;
+ margin: 0px;
+ width: 958px;
+ }
+
+ header {
+ color: #F0F0F0;
+ background: #C52F24;
+ padding: 0.5em 1.5em;
+ }
+
+ h2 {
+ color: #C52F24;
+ line-height: 25px;
+ }
+
+ .details {
+ border: 1px solid #D0D0D0;
+ border-radius: 4px;
+ margin: 1em 0px;
+ display: block;
+ width: 978px;
+ }
+
+ .summary {
+ padding: 8px 15px;
+ border-bottom: 1px solid #D0D0D0;
+ display: block;
+ }
+
+ .details pre {
+ margin: 5px;
+ border: none;
+ }
+
+ #container {
+ box-sizing: border-box;
+ width: 100%;
+ padding: 0 1.5em;
+ }
+
+ .source * {
+ margin: 0px;
+ padding: 0px;
+ }
+
+ .source {
+ border: 1px solid #D9D9D9;
+ background: #ECECEC;
+ width: 978px;
+ }
+
+ .source pre {
+ padding: 10px 0px;
+ border: none;
+ }
+
+ .source .data {
+ font-size: 80%;
+ overflow: auto;
+ background-color: #FFF;
+ }
+
+ .info {
+ padding: 0.5em;
+ }
+
+ .source .data .line_numbers {
+ background-color: #ECECEC;
+ color: #AAA;
+ padding: 1em .5em;
+ border-right: 1px solid #DDD;
+ text-align: right;
+ }
+
+ .line {
+ padding-left: 10px;
+ }
+
+ .line:hover {
+ background-color: #F6F6F6;
+ }
+
+ .line.active {
+ background-color: #FFCCCC;
+ }
+
+ a { color: #980905; }
a:visited { color: #666; }
- a:hover { color: #fff; background-color:#000; }
+ a:hover { color: #C52F24; }
+
+ <%= yield :style %>
</style>
+
+ <script>
+ var toggle = function(id) {
+ var s = document.getElementById(id).style;
+ s.display = s.display == 'none' ? 'block' : 'none';
+ }
+ var show = function(id) {
+ document.getElementById(id).style.display = 'block';
+ }
+ var hide = function(id) {
+ document.getElementById(id).style.display = 'none';
+ }
+ var toggleTrace = function() {
+ toggle('blame_trace');
+ }
+ var toggleSessionDump = function() {
+ toggle('session_dump');
+ }
+ var toggleEnvDump = function() {
+ toggle('env_dump');
+ }
+ </script>
</head>
<body>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
index dbfdf76947..ca14215946 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb
@@ -1,2 +1,7 @@
-<h1>Template is missing</h1>
-<p><%=h @exception.message %></p>
+<header>
+ <h1>Template is missing</h1>
+</header>
+
+<div id="container">
+ <h2><%= @exception.message %></h2>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index a357a7ba11..61690d3e50 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -1,23 +1,30 @@
-<h1>Routing Error</h1>
-<p><pre><%=h @exception.message %></pre></p>
-<% unless @exception.failures.empty? %>
- <p>
- <h2>Failure reasons:</h2>
- <ol>
- <% @exception.failures.each do |route, reason| %>
- <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
- <% end %>
- </ol>
- </p>
-<% end %>
-<%= render template: "rescues/_trace" %>
+<header>
+ <h1>Routing Error</h1>
+</header>
+<div id="container">
+ <h2><%= @exception.message %></h2>
+ <% unless @exception.failures.empty? %>
+ <p>
+ <h2>Failure reasons:</h2>
+ <ol>
+ <% @exception.failures.each do |route, reason| %>
+ <li><code><%= route.inspect.gsub('\\', '') %></code> failed because <%= reason.downcase %></li>
+ <% end %>
+ </ol>
+ </p>
+ <% end %>
-<h2>
- Routes
-</h2>
+ <%= render template: "rescues/_trace" %>
-<p>
- Routes match in priority from top to bottom
-</p>
+ <% if @routes_inspector %>
+ <h2>
+ Routes
+ </h2>
-<p><pre><%= @routes %></pre></p>
+ <p>
+ Routes match in priority from top to bottom
+ </p>
+
+ <%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %>
+ <% end %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
index a1b377f68c..63216ef7c5 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
@@ -1,17 +1,43 @@
-<h1>
- <%=h @exception.original_exception.class.to_s %> in
- <%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %>
-</h1>
+<% @source_extract = @exception.source_extract(0, :html) %>
+<header>
+ <h1>
+ <%= @exception.original_exception.class.to_s %> in
+ <%= @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%= @request.parameters["action"] %>
+ </h1>
+</header>
-<p>
- Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised:
- <pre><code><%=h @exception.message %></code></pre>
-</p>
+<div id="container">
+ <p>
+ Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised:
+ </p>
+ <pre><code><%= @exception.message %></code></pre>
-<p>Extracted source (around line <b>#<%=h @exception.line_number %></b>):
-<pre><code><%=h @exception.source_extract %></code></pre></p>
+ <div class="source">
+ <div class="info">
+ <p>Extracted source (around line <strong>#<%= @exception.line_number %></strong>):</p>
+ </div>
+ <div class="data">
+ <table cellpadding="0" cellspacing="0" class="lines">
+ <tr>
+ <td>
+ <pre class="line_numbers">
+ <% @source_extract.keys.each do |line_number| %>
+<span><%= line_number -%></span>
+ <% end %>
+ </pre>
+ </td>
+<td width="100%">
+<pre>
+<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @exception.line_number -%>"><%= source -%></div><% end -%>
+</pre>
+</td>
+ </tr>
+ </table>
+</div>
+</div>
-<p><%=h @exception.sub_template_message %></p>
+ <p><%= @exception.sub_template_message %></p>
-<%= render template: "rescues/_trace" %>
-<%= render template: "rescues/_request_and_response" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
index 683379da10..c1fbf67eed 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb
@@ -1,2 +1,6 @@
-<h1>Unknown action</h1>
-<p><%=h @exception.message %></p>
+<header>
+ <h1>Unknown action</h1>
+</header>
+<div id="container">
+ <h2><%= @exception.message %></h2>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb
new file mode 100644
index 0000000000..400ae97d22
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb
@@ -0,0 +1,16 @@
+<tr class='route_row' data-helper='path'>
+ <td data-route-name='<%= route[:name] %>'>
+ <% if route[:name].present? %>
+ <%= route[:name] %><span class='helper'>_path</span>
+ <% end %>
+ </td>
+ <td data-route-verb='<%= route[:verb] %>'>
+ <%= route[:verb] %>
+ </td>
+ <td data-route-path='<%= route[:path] %>'>
+ <%= route[:path] %>
+ </td>
+ <td data-route-reqs='<%= route[:reqs] %>'>
+ <%= route[:reqs] %>
+ </td>
+</tr>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
new file mode 100644
index 0000000000..9026c4eeb2
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -0,0 +1,56 @@
+<% content_for :style do %>
+ #route_table td { padding: 0 30px; }
+ #route_table { margin: 0 auto 0; }
+<% end %>
+
+<table id='route_table' class='route_table'>
+ <thead>
+ <tr>
+ <th>Helper<br />
+ <%= link_to "Path", "#", 'data-route-helper' => '_path',
+ title: "Returns a relative path (without the http or domain)" %> /
+ <%= link_to "Url", "#", 'data-route-helper' => '_url',
+ title: "Returns an absolute url (with the http and domain)" %>
+ </th>
+ <th>HTTP Verb</th>
+ <th>Path</th>
+ <th>Controller#Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ <%= yield %>
+ </tbody>
+</table>
+
+<script type='text/javascript'>
+ function each(elems, func) {
+ if (!elems instanceof Array) { elems = [elems]; }
+ for (var i = elems.length; i--; ) {
+ func(elems[i]);
+ }
+ }
+
+ function setValOn(elems, val) {
+ each(elems, function(elem) {
+ elem.innerHTML = val;
+ });
+ }
+
+ function onClick(elems, func) {
+ each(elems, function(elem) {
+ elem.onclick = func;
+ });
+ }
+
+ // Enables functionality to toggle between `_path` and `_url` helper suffixes
+ function setupRouteToggleHelperLinks() {
+ var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]');
+ onClick(toggleLinks, function(){
+ var helperTxt = this.getAttribute("data-route-helper");
+ var helperElems = document.querySelectorAll('[data-route-name] span.helper');
+ setValOn(helperElems, helperTxt);
+ });
+ }
+
+ setupRouteToggleHelperLinks();
+</script>
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 98c87d9b2d..5a835ae439 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -1,7 +1,7 @@
require "action_dispatch"
module ActionDispatch
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie # :nodoc:
config.action_dispatch = ActiveSupport::OrderedOptions.new
config.action_dispatch.x_sendfile_header = nil
config.action_dispatch.ip_spoofing_check = true
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 4417cb841a..d55eb8109a 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -191,8 +191,6 @@ module ActionDispatch
# <tt>:any</tt> which means that the route will respond to any of the HTTP
# methods.
#
- # Examples:
- #
# match 'post/:id' => 'posts#show', via: :get
# match 'post/:id' => 'posts#create_comment', via: :post
#
@@ -204,8 +202,6 @@ module ActionDispatch
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
- # Examples:
- #
# get 'post/:id' => 'posts#show'
# post 'post/:id' => 'posts#create_comment'
#
diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb
index c18dc94d4f..ea3e8357d4 100644
--- a/actionpack/lib/action_dispatch/routing/inspector.rb
+++ b/actionpack/lib/action_dispatch/routing/inspector.rb
@@ -51,7 +51,7 @@ module ActionDispatch
end
def internal?
- path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}}
+ controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
end
def engine?
@@ -61,32 +61,46 @@ module ActionDispatch
##
# This class is just used for displaying route information when someone
- # executes `rake routes`. People should not use this class.
+ # executes `rake routes` or looks at the RoutingError page.
+ # People should not use this class.
class RoutesInspector # :nodoc:
- def initialize
- @engines = Hash.new
+ def initialize(routes)
+ @engines = {}
+ @routes = routes
end
- def format(all_routes, filter = nil)
- if filter
- all_routes = all_routes.select{ |route| route.defaults[:controller] == filter }
+ def format(formatter, filter = nil)
+ routes_to_display = filter_routes(filter)
+
+ routes = collect_routes(routes_to_display)
+ formatter.section :application, 'Application routes', routes
+
+ @engines.each do |name, engine_routes|
+ formatter.section :engine, "Routes for #{name}", engine_routes
end
- routes = collect_routes(all_routes)
+ formatter.result
+ end
+
+ private
- formatted_routes(routes) +
- formatted_routes_for_engines
+ def filter_routes(filter)
+ if filter
+ @routes.select { |route| route.defaults[:controller] == filter }
+ else
+ @routes
+ end
end
def collect_routes(routes)
- routes = routes.collect do |route|
+ routes.collect do |route|
RouteWrapper.new(route)
end.reject do |route|
route.internal?
end.collect do |route|
collect_engine_routes(route)
- {:name => route.name, :verb => route.verb, :path => route.path, :reqs => route.reqs }
+ { name: route.name, verb: route.verb, path: route.path, reqs: route.reqs }
end
end
@@ -100,21 +114,49 @@ module ActionDispatch
@engines[name] = collect_routes(routes.routes)
end
end
+ end
- def formatted_routes_for_engines
- @engines.map do |name, routes|
- ["\nRoutes for #{name}:"] + formatted_routes(routes)
- end.flatten
+ class ConsoleFormatter
+ def initialize
+ @buffer = []
end
- def formatted_routes(routes)
- name_width = routes.map{ |r| r[:name].length }.max
- verb_width = routes.map{ |r| r[:verb].length }.max
- path_width = routes.map{ |r| r[:path].length }.max
+ def result
+ @buffer.join("\n")
+ end
- routes.map do |r|
- "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
+ def section(type, title, routes)
+ @buffer << "\n#{title}:" unless type == :application
+ @buffer << draw_section(routes)
+ end
+
+ private
+ def draw_section(routes)
+ name_width = routes.map { |r| r[:name].length }.max
+ verb_width = routes.map { |r| r[:verb].length }.max
+ path_width = routes.map { |r| r[:path].length }.max
+
+ routes.map do |r|
+ "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
+ end
end
+ end
+
+ class HtmlTableFormatter
+ def initialize(view)
+ @view = view
+ @buffer = []
+ end
+
+ def section(type, title, routes)
+ @buffer << %(<tr><th colspan="4">#{title}</th></tr>)
+ @buffer << @view.render(partial: "routes/route", collection: routes)
+ end
+
+ def result
+ @view.raw @view.render(layout: "routes/table") {
+ @view.raw @buffer.join("\n")
+ }
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index d6fe436b68..a21383e091 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -26,15 +26,10 @@ module ActionDispatch
def matches?(env)
req = @request.new(env)
- @constraints.each { |constraint|
- if constraint.respond_to?(:matches?) && !constraint.matches?(req)
- return false
- elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
- return false
- end
- }
-
- return true
+ @constraints.none? do |constraint|
+ (constraint.respond_to?(:matches?) && !constraint.matches?(req)) ||
+ (constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req)))
+ end
ensure
req.reset_parameters
end
@@ -152,13 +147,13 @@ module ActionDispatch
end
def conditions
- { :path_info => @path }.merge(constraints).merge(request_method_condition)
+ { :path_info => @path }.merge!(constraints).merge!(request_method_condition)
end
def requirements
@requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements|
requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
- @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
+ @options.each { |k, v| requirements[k] ||= v if v.is_a?(Regexp) }
end
end
@@ -252,7 +247,7 @@ module ActionDispatch
def defaults_from_constraints(constraints)
url_keys = [:protocol, :subdomain, :domain, :host, :port]
- constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
+ constraints.select { |k, v| url_keys.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) }
end
end
@@ -285,7 +280,7 @@ module ActionDispatch
# of most Rails applications, this is beneficial.
def root(options = {})
options = { :to => options } if options.is_a?(String)
- match '/', { :as => :root, :via => :get }.merge(options)
+ match '/', { :as => :root, :via => :get }.merge!(options)
end
# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -315,7 +310,7 @@ module ActionDispatch
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id', to: lambda {|hash| [200, {}, "Coming soon"] }
+ # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
# match 'photos/:id', to: PhotoRackApp
# # Yes, controller actions are just rack endpoints
# match 'photos/:id', to: PhotosController.action(:show)
@@ -360,7 +355,7 @@ module ActionDispatch
# +call+ or a string representing a controller's action.
#
# match 'path', to: 'controller#action'
- # match 'path', to: lambda { |env| [200, {}, "Success!"] }
+ # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
# match 'path', to: RackApp
#
# [:on]
@@ -624,8 +619,6 @@ module ActionDispatch
#
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
#
- # === Examples
- #
# # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
# scope module: "admin" do
# resources :posts
@@ -641,19 +634,16 @@ module ActionDispatch
# resources :posts
# end
def scope(*args)
- options = args.extract_options!
- options = options.dup
-
- options[:path] = args.first if args.first.is_a?(String)
+ options = args.extract_options!.dup
recover = {}
+ options[:path] = args.flatten.join('/') if args.any?
options[:constraints] ||= {}
- unless options[:constraints].is_a?(Hash)
- block, options[:constraints] = options[:constraints], {}
- end
if options[:constraints].is_a?(Hash)
(options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(options[:constraints]))
+ else
+ block, options[:constraints] = options[:constraints], {}
end
scope_options.each do |option|
@@ -663,8 +653,8 @@ module ActionDispatch
end
end
- recover[:block] = @scope[:blocks]
- @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
+ recover[:blocks] = @scope[:blocks]
+ @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
recover[:options] = @scope[:options]
@scope[:options] = merge_options_scope(@scope[:options], options)
@@ -672,12 +662,7 @@ module ActionDispatch
yield
self
ensure
- scope_options.each do |option|
- @scope[option] = recover[option] if recover.has_key?(option)
- end
-
- @scope[:options] = recover[:options]
- @scope[:blocks] = recover[:block]
+ @scope.merge!(recover)
end
# Scopes routes to a specific controller
@@ -714,8 +699,6 @@ module ActionDispatch
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
# <tt>Resources#resources</tt>.
#
- # === Examples
- #
# # accessible through /sekret/posts rather than /admin/posts
# namespace :admin, path: "sekret" do
# resources :posts
@@ -853,7 +836,7 @@ module ActionDispatch
end
def merge_options_scope(parent, child) #:nodoc:
- (parent || {}).except(*override_keys(child)).merge(child)
+ (parent || {}).except(*override_keys(child)).merge!(child)
end
def merge_shallow_scope(parent, child) #:nodoc:
@@ -866,7 +849,7 @@ module ActionDispatch
def defaults_from_constraints(constraints)
url_keys = [:protocol, :subdomain, :domain, :host, :port]
- constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
+ constraints.select { |k, v| url_keys.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) }
end
end
@@ -1060,15 +1043,7 @@ module ActionDispatch
get :new
end if parent_resource.actions.include?(:new)
- member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- if parent_resource.actions.include?(:update)
- patch :update
- put :update
- end
- delete :destroy if parent_resource.actions.include?(:destroy)
- end
+ set_member_mappings_for_resource
end
self
@@ -1227,15 +1202,7 @@ module ActionDispatch
get :new
end if parent_resource.actions.include?(:new)
- member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- if parent_resource.actions.include?(:update)
- patch :update
- put :update
- end
- delete :destroy if parent_resource.actions.include?(:destroy)
- end
+ set_member_mappings_for_resource
end
self
@@ -1586,6 +1553,18 @@ module ActionDispatch
end
end
end
+
+ def set_member_mappings_for_resource
+ member do
+ get :edit if parent_resource.actions.include?(:edit)
+ get :show if parent_resource.actions.include?(:show)
+ if parent_resource.actions.include?(:update)
+ patch :update
+ put :update
+ end
+ delete :destroy if parent_resource.actions.include?(:destroy)
+ end
+ end
end
# Routing Concerns allow you to declare common routes that can be reused
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 497ac3d545..6d3f8da932 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -74,8 +74,6 @@ module ActionDispatch
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
- # ==== Examples
- #
# # an Article record
# polymorphic_url(record) # same as article_url(record)
#
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index d70063d0e9..d751e04e6a 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -75,7 +75,7 @@ module ActionDispatch
:port => request.optional_port,
:path => request.path,
:params => request.query_parameters
- }.merge options
+ }.merge! options
if !params.empty? && url_options[:path].match(/%\{\w*\}/)
url_options[:path] = (url_options[:path] % escape_path(params))
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 0f95daa790..b1959e388c 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,5 +1,6 @@
-require 'journey'
+require 'action_dispatch/journey'
require 'forwardable'
+require 'thread_safe'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/module/remove_method'
@@ -20,7 +21,7 @@ module ActionDispatch
def initialize(options={})
@defaults = options[:defaults]
@glob_param = options.delete(:glob)
- @controllers = {}
+ @controller_class_names = ThreadSafe::Cache.new
end
def call(env)
@@ -68,13 +69,8 @@ module ActionDispatch
private
def controller_reference(controller_param)
- controller_name = "#{controller_param.camelize}Controller"
-
- unless controller = @controllers[controller_param]
- controller = @controllers[controller_param] =
- ActiveSupport::Dependencies.reference(controller_name)
- end
- controller.get(controller_name)
+ const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
+ ActiveSupport::Dependencies.constantize(const_name)
end
def dispatch(controller, action, env)
@@ -130,6 +126,12 @@ module ActionDispatch
end
def clear!
+ @helpers.each do |helper|
+ @module.module_eval do
+ remove_possible_method helper
+ end
+ end
+
@routes.clear
@helpers.clear
end
@@ -288,7 +290,6 @@ module ActionDispatch
def clear!
@finalized = false
- @url_helpers = nil
named_routes.clear
set.clear
formatter.clear
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 76311c423a..8e19025722 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -130,6 +130,7 @@ module ActionDispatch
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:anchor</tt> - An anchor name to be appended to the path.
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/"
+ # * <tt>:script_name</tt> - Specifies application path relative to domain root. If provided, prepends application path.
#
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
@@ -142,6 +143,10 @@ module ActionDispatch
# # => 'http://somehost.org/tasks/testing/'
# url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33'
# # => 'http://somehost.org/tasks/testing?number=33'
+ # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp"
+ # # => 'http://somehost.org/myapp/tasks/testing'
+ # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true
+ # # => '/myapp/tasks/testing'
def url_for(options = nil)
case options
when nil
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
index 6c61d4e61a..8f90a1223e 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
@@ -20,7 +20,7 @@ module ActionDispatch
def assert_dom_not_equal(expected, actual, message = "")
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
- refute_equal expected_dom, actual_dom
+ assert_not_equal expected_dom, actual_dom
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 79dff7d121..9210bffd1d 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -1,5 +1,6 @@
require 'uri'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/string/access'
require 'action_controller/metal/exceptions'
module ActionDispatch
@@ -208,11 +209,9 @@ module ActionDispatch
end
def fail_on(exception_class)
- begin
- yield
- rescue exception_class => e
- raise MiniTest::Assertion, e.message
- end
+ yield
+ rescue exception_class => e
+ raise MiniTest::Assertion, e.message
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 2207a43afc..e481f3b245 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -155,8 +155,6 @@ module ActionDispatch
# If the method is called with a block, once all equality tests are
# evaluated the block is called with an array of all matched elements.
#
- # ==== Examples
- #
# # At least one form element
# assert_select "form"
#
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 95cd89a166..1fc5933e98 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -491,9 +491,6 @@ module ActionDispatch
include ActionController::TemplateAssertions
include ActionDispatch::Routing::UrlFor
- # Use AD::IntegrationTest for acceptance tests
- register_spec_type(/(Acceptance|Integration) ?Test\z/i, self)
-
@@app = nil
def self.app
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 9ad5a1bc1d..e657283cec 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -26,17 +26,19 @@ module ActionDispatch
@response.redirect_url
end
- # Shortcut for <tt>Rack::Test::UploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>:
+ # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), type)</tt>:
#
- # post :change_avatar, avatar: fixture_file_upload('/files/spongebob.png', 'image/png')
+ # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
#
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
# This will not affect other platforms:
#
- # post :change_avatar, avatar: fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
+ # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary)
def fixture_file_upload(path, mime_type = nil, binary = false)
- fixture_path = self.class.fixture_path if self.class.respond_to?(:fixture_path)
- Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
+ if self.class.respond_to?(:fixture_path) && self.class.fixture_path
+ path = File.join(self.class.fixture_path, path)
+ end
+ Rack::Test::UploadedFile.new(path, mime_type, binary)
end
end
end
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 39c7faf740..ad5acd8080 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 8bbf52382a..4aafbcb655 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb
index 1c6eaf36f7..f3f6b425a8 100644
--- a/actionpack/lib/action_view/digestor.rb
+++ b/actionpack/lib/action_view/digestor.rb
@@ -1,8 +1,8 @@
-require 'mutex_m'
+require 'thread_safe'
module ActionView
class Digestor
- EXPLICIT_DEPENDENCY = /# Template Dependency: ([^ ]+)/
+ EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
# Matches:
# render partial: "comments/comment", collection: commentable.comments
@@ -21,23 +21,12 @@ module ActionView
/x
cattr_reader(:cache)
- @@cache = Hash.new.extend Mutex_m
+ @@cache = ThreadSafe::Cache.new
def self.digest(name, format, finder, options = {})
- cache.synchronize do
- unsafe_digest name, format, finder, options
- end
- end
-
- ###
- # This method is NOT thread safe. DO NOT CALL IT DIRECTLY, instead call
- # Digestor.digest
- def self.unsafe_digest(name, format, finder, options = {}) # :nodoc:
- key = "#{name}.#{format}"
-
- cache.fetch(key) do
+ @@cache["#{name}.#{format}"] ||= begin
klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor
- cache[key] = klass.new(name, format, finder).digest
+ klass.new(name, format, finder).digest
end
end
@@ -93,7 +82,7 @@ module ActionView
def dependency_digest
dependencies.collect do |template_name|
- Digestor.unsafe_digest(template_name, format, finder, partial: true)
+ Digestor.digest(template_name, format, finder, partial: true)
end.join("-")
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 29a5ccedc1..11743e36f2 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -32,6 +32,9 @@ module ActionView
# You can modify the HTML attributes of the script tag by passing a hash as the
# last argument.
#
+ # When the Asset Pipeline is enabled, you can pass the name of your manifest as
+ # source, and include other JavaScript or CoffeeScript files inside the manifest.
+ #
# javascript_include_tag "xmlhr"
# # => <script src="/assets/xmlhr.js?1284139606"></script>
#
@@ -50,9 +53,11 @@ module ActionView
#
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
+ path_options = options.extract!('protocol').symbolize_keys
+
sources.uniq.map { |source|
tag_options = {
- "src" => path_to_javascript(source)
+ "src" => path_to_javascript(source, path_options)
}.merge(options)
content_tag(:script, "", tag_options)
}.join("\n").html_safe
@@ -86,11 +91,13 @@ module ActionView
#
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
+ path_options = options.extract!('protocol').symbolize_keys
+
sources.uniq.map { |source|
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
- "href" => path_to_stylesheet(source)
+ "href" => path_to_stylesheet(source, path_options)
}.merge(options)
tag(:link, tag_options)
}.join("\n").html_safe
@@ -106,19 +113,18 @@ module ActionView
# * <tt>:type</tt> - Override the auto-generated mime type
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
#
- # ==== Examples
- # auto_discovery_link_tag
- # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:atom)
- # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
- # auto_discovery_link_tag(:rss, {action: "feed"})
- # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
- # # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
- # auto_discovery_link_tag(:rss, {controller: "news", action: "feed"})
- # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
- # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
- # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
+ # auto_discovery_link_tag
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:atom)
+ # # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
+ # auto_discovery_link_tag(:rss, {action: "feed"})
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
+ # # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
+ # auto_discovery_link_tag(:rss, {controller: "news", action: "feed"})
+ # # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
+ # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
+ # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" />
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
if !(type == :rss || type == :atom) && tag_options[:type].blank?
message = "You have passed type other than :rss or :atom to auto_discovery_link_tag and haven't supplied " +
@@ -137,27 +143,24 @@ module ActionView
)
end
- # <%= favicon_link_tag %>
- #
- # generates
- #
- # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # Returns a link loading a favicon file. You may specify a different file
+ # in the first argument. The helper accepts an additional options hash where
+ # you can override "rel" and "type".
#
- # You may specify a different file in the first argument:
- #
- # <%= favicon_link_tag '/myicon.ico' %>
- #
- # That's passed to +path_to_image+ as is, so it gives
- #
- # <link href="/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # ==== Options
+ # * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon'
+ # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon'
#
- # The helper accepts an additional options hash where you can override "rel" and "type".
+ # favicon_link_tag '/myicon.ico'
+ # # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
- # For example, Mobile Safari looks for a different LINK tag, pointing to an image that
+ # Mobile Safari looks for a different <link> tag, pointing to an image that
# will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad.
# The following call would generate such a tag:
#
- # <%= favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' %>
+ # favicon_link_tag '/mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
+ # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
+ #
def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
@@ -166,7 +169,7 @@ module ActionView
}.merge(options.symbolize_keys))
end
- # Returns an html image tag for the +source+. The +source+ can be a full
+ # Returns an HTML image tag for the +source+. The +source+ can be a full
# path or a file.
#
# ==== Options
@@ -179,18 +182,18 @@ module ActionView
# width="30" and height="45", and "50" becomes width="50" and height="50".
# <tt>:size</tt> will be ignored if the value is not in the correct format.
#
- # image_tag("icon")
- # # => <img alt="Icon" src="/assets/icon" />
- # image_tag("icon.png")
- # # => <img alt="Icon" src="/assets/icon.png" />
- # image_tag("icon.png", size: "16x10", alt: "Edit Entry")
- # # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
- # image_tag("/icons/icon.gif", size: "16")
- # # => <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
- # image_tag("/icons/icon.gif", height: '32', width: '32')
- # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
- # image_tag("/icons/icon.gif", class: "menu_icon")
- # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
+ # image_tag("icon")
+ # # => <img alt="Icon" src="/assets/icon" />
+ # image_tag("icon.png")
+ # # => <img alt="Icon" src="/assets/icon.png" />
+ # image_tag("icon.png", size: "16x10", alt: "Edit Entry")
+ # # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
+ # image_tag("/icons/icon.gif", size: "16")
+ # # => <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
+ # image_tag("/icons/icon.gif", height: '32', width: '32')
+ # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
+ # image_tag("/icons/icon.gif", class: "menu_icon")
+ # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
def image_tag(source, options={})
options = options.symbolize_keys
@@ -208,6 +211,9 @@ module ActionView
tag("img", options)
end
+ # Returns a string suitable for an html image tag alt attribute.
+ # +src+ is meant to be an image file path.
+ # It removes the basename of the file path and the digest, if any.
def image_alt(src)
File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize
end
@@ -228,24 +234,24 @@ module ActionView
# width="30" and height="45". <tt>:size</tt> will be ignored if the
# value is not in the correct format.
#
- # video_tag("trailer")
- # # => <video src="/videos/trailer" />
- # video_tag("trailer.ogg")
- # # => <video src="/videos/trailer.ogg" />
- # video_tag("trailer.ogg", controls: true, autobuffer: true)
- # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
- # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
- # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
- # video_tag("/trailers/hd.avi", size: "16x16")
- # # => <video src="/trailers/hd.avi" width="16" height="16" />
- # video_tag("/trailers/hd.avi", height: '32', width: '32')
- # # => <video height="32" src="/trailers/hd.avi" width="32" />
- # video_tag("trailer.ogg", "trailer.flv")
- # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"])
- # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
- # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag("trailer")
+ # # => <video src="/videos/trailer" />
+ # video_tag("trailer.ogg")
+ # # => <video src="/videos/trailer.ogg" />
+ # video_tag("trailer.ogg", controls: true, autobuffer: true)
+ # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
+ # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
+ # video_tag("/trailers/hd.avi", size: "16x16")
+ # # => <video src="/trailers/hd.avi" width="16" height="16" />
+ # video_tag("/trailers/hd.avi", height: '32', width: '32')
+ # # => <video height="32" src="/trailers/hd.avi" width="32" />
+ # video_tag("trailer.ogg", "trailer.flv")
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag(["trailer.ogg", "trailer.flv"])
+ # # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
+ # # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
def video_tag(*sources)
multiple_sources_tag('video', sources) do |options|
options[:poster] = path_to_image(options[:poster]) if options[:poster]
@@ -256,18 +262,18 @@ module ActionView
end
end
- # Returns an html audio tag for the +source+.
+ # Returns an HTML audio tag for the +source+.
# The +source+ can be full path or file that exists in
# your public audios directory.
#
- # audio_tag("sound") # =>
- # <audio src="/audios/sound" />
- # audio_tag("sound.wav") # =>
- # <audio src="/audios/sound.wav" />
- # audio_tag("sound.wav", autoplay: true, controls: true) # =>
- # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
- # audio_tag("sound.wav", "sound.mid") # =>
- # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
+ # audio_tag("sound")
+ # # => <audio src="/audios/sound" />
+ # audio_tag("sound.wav")
+ # # => <audio src="/audios/sound.wav" />
+ # audio_tag("sound.wav", autoplay: true, controls: true)
+ # # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
+ # audio_tag("sound.wav", "sound.mid")
+ # # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
def audio_tag(*sources)
multiple_sources_tag('audio', sources)
end
diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb
index 0bb5e739bb..0affac41e8 100644
--- a/actionpack/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_url_helper.rb
@@ -2,7 +2,7 @@ require 'zlib'
module ActionView
# = Action View Asset URL Helpers
- module Helpers #:nodoc:
+ module Helpers
# This module provides methods for generating asset paths and
# urls.
#
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index f5ac455208..42b1dd8933 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -2,7 +2,7 @@ require 'set'
module ActionView
# = Action View Atom Feed Helpers
- module Helpers #:nodoc:
+ module Helpers
module AtomFeedHelper
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
# template languages).
@@ -124,7 +124,7 @@ module ActionView
end
end
- class AtomBuilder
+ class AtomBuilder #:nodoc:
XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
def initialize(xml)
@@ -158,7 +158,7 @@ module ActionView
end
end
- class AtomFeedBuilder < AtomBuilder
+ class AtomFeedBuilder < AtomBuilder #:nodoc:
def initialize(xml, view, feed_options = {})
@xml, @view, @feed_options = xml, view, feed_options
end
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
index dfdd5a786d..87fbf8f1a8 100644
--- a/actionpack/lib/action_view/helpers/benchmark_helper.rb
+++ b/actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -2,7 +2,7 @@ require 'active_support/benchmarkable'
module ActionView
module Helpers
- module BenchmarkHelper
+ module BenchmarkHelper #:nodoc:
include ActiveSupport::Benchmarkable
def benchmark(*)
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index ddac87a37d..995aa10afb 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -52,6 +52,13 @@ module ActionView
# Additionally, the digestor will automatically look through your template file for
# explicit and implicit dependencies, and include those as part of the digest.
#
+ # The digestor can be bypassed by passing skip_digest: true as an option to the cache call:
+ #
+ # <% cache project, skip_digest: true do %>
+ # <b>All the topics on this project</b>
+ # <%= render project.topics %>
+ # <% end %>
+ #
# ==== Implicit dependencies
#
# Most template dependencies can be derived from calls to render in the template itself.
@@ -105,7 +112,23 @@ module ActionView
# Now all you'll have to do is change that timestamp when the helper method changes.
def cache(name = {}, options = nil, &block)
if controller.perform_caching
- safe_concat(fragment_for(fragment_name_with_digest(name), options, &block))
+ safe_concat(fragment_for(cache_fragment_name(name, options), options, &block))
+ else
+ yield
+ end
+
+ nil
+ end
+
+ # Cache fragments of a view if +condition+ is true
+ #
+ # <%= cache_if admin?, project do %>
+ # <b>All the topics on this project</b>
+ # <%= render project.topics %>
+ # <% end %>
+ def cache_if(condition, name = {}, options = nil, &block)
+ if condition
+ cache(name, options, &block)
else
yield
end
@@ -113,6 +136,33 @@ module ActionView
nil
end
+ # Cache fragments of a view unless +condition+ is true
+ #
+ # <%= cache_unless admin?, project do %>
+ # <b>All the topics on this project</b>
+ # <%= render project.topics %>
+ # <% end %>
+ def cache_unless(condition, name = {}, options = nil, &block)
+ cache_if !condition, name, options, &block
+ end
+
+ # This helper returns the name of a cache key for a given fragment cache
+ # call. By supplying skip_digest: true to cache, the digestion of cache
+ # fragments can be manually bypassed. This is useful when cache fragments
+ # cannot be manually expired unless you know the exact key which is the
+ # case when using memcached.
+ def cache_fragment_name(name = {}, options = nil)
+ skip_digest = options && options[:skip_digest]
+
+ if skip_digest
+ name
+ else
+ fragment_name_with_digest(name)
+ end
+ end
+
+ private
+
def fragment_name_with_digest(name) #:nodoc:
if @virtual_path
[
@@ -124,7 +174,6 @@ module ActionView
end
end
- private
# TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
if fragment = controller.read_fragment(name, options)
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 85e398e559..4ec860d69a 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -42,14 +42,12 @@ module ActionView
end
# Calling content_for stores a block of markup in an identifier for later use.
- # You can make subsequent calls to the stored content in other templates, helper modules
- # or the layout by passing the identifier as an argument to <tt>content_for</tt>.
+ # In order to access this stored content in other templates, helper modules
+ # or the layout, you would pass the identifier as an argument to <tt>content_for</tt>.
#
# Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling
# <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does.
#
- # ==== Examples
- #
# <% content_for :not_authorized do %>
# alert('You are not authorized to do that!')
# <% end %>
@@ -74,7 +72,8 @@ module ActionView
#
# <%= stored_content %>
#
- # You can use the <tt>yield</tt> syntax alongside an existing call to <tt>yield</tt> in a layout. For example:
+ # You can also use the <tt>yield</tt> syntax alongside an existing call to
+ # <tt>yield</tt> in a layout. For example:
#
# <%# This is the layout %>
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
@@ -115,7 +114,7 @@ module ActionView
# <li><%= link_to 'Home', action: 'index' %></li>
# <% end %>
#
- # <%# Add some other content, or use a different template: %>
+ # And in other place:
#
# <% content_for :navigation do %>
# <li><%= link_to 'Login', action: 'login' %></li>
@@ -145,8 +144,7 @@ module ActionView
#
# <% content_for :script, javascript_include_tag(:defaults) %>
#
- # WARNING: content_for is ignored in caches. So you shouldn't use it
- # for elements that will be fragment cached.
+ # WARNING: content_for is ignored in caches. So you shouldn't use it for elements that will be fragment cached.
def content_for(name, content = nil, options = {}, &block)
if content || block_given?
if block_given?
@@ -173,13 +171,9 @@ module ActionView
result unless content
end
- # content_for? simply checks whether any content has been captured yet using content_for
+ # content_for? checks whether any content has been captured yet using `content_for`.
# Useful to render parts of your layout differently based on what is in your views.
#
- # ==== Examples
- #
- # Perhaps you will use different css in you layout if no content_for :right_column
- #
# <%# This is the layout %>
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
# <head>
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 6e51ba66a5..1fbf61a5a9 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -45,7 +45,6 @@ module ActionView
# 40-59 secs # => less than a minute
# 60-89 secs # => 1 minute
#
- # ==== Examples
# from_time = Time.now
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
@@ -166,7 +165,6 @@ module ActionView
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
# attribute (identified by +method+) on an object assigned to the template (identified by +object+).
#
- #
# ==== Options
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
# "2" instead of "February").
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index d361a69a92..34fc23ac1a 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -27,14 +27,12 @@ module ActionView
# new_record: true
# </pre>
def debug(object)
- begin
- Marshal::dump(object)
- object = ERB::Util.html_escape(object.to_yaml).gsub(" ", "&nbsp; ").html_safe
- content_tag(:pre, object, :class => "debug_dump")
- rescue Exception # errors from Marshal or YAML
- # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
- content_tag(:code, object.to_yaml, :class => "debug_dump")
- end
+ Marshal::dump(object)
+ object = ERB::Util.html_escape(object.to_yaml).gsub(" ", "&nbsp; ").html_safe
+ content_tag(:pre, object, :class => "debug_dump")
+ rescue Exception # errors from Marshal or YAML
+ # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
+ content_tag(:code, object.to_yaml, :class => "debug_dump")
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6abf1e1751..8a1e886b7f 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -101,9 +101,9 @@ module ActionView
# and generate HTML accordingly.
#
# The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
- # passed to <tt>Person#update_attributes</tt>:
+ # passed to <tt>Person#update</tt>:
#
- # if @person.update_attributes(params[:person])
+ # if @person.update(params[:person])
# # success
# else
# # error handling
@@ -388,9 +388,9 @@ module ActionView
# In many cases you will want to wrap the above in another helper, so you
# could do something like the following:
#
- # def labelled_form_for(record_or_name_or_array, *args, &proc)
+ # def labelled_form_for(record_or_name_or_array, *args, &block)
# options = args.extract_options!
- # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &proc)
+ # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block)
# end
#
# If you don't need to attach a form to a model instance, then check out
@@ -412,10 +412,9 @@ module ActionView
# <%= form_for @invoice, url: external_url, authenticity_token: false do |f|
# ...
# <% end %>
- def form_for(record, options = {}, &proc)
+ def form_for(record, options = {}, &block)
raise ArgumentError, "Missing block" unless block_given?
-
- options[:html] ||= {}
+ html_options = options[:html] ||= {}
case record
when String, Symbol
@@ -428,17 +427,16 @@ module ActionView
apply_form_for_options!(record, object, options)
end
- options[:html][:data] = options.delete(:data) if options.has_key?(:data)
- options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
- options[:html][:method] = options.delete(:method) if options.has_key?(:method)
- options[:html][:authenticity_token] = options.delete(:authenticity_token)
+ html_options[:data] = options.delete(:data) if options.has_key?(:data)
+ html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
+ html_options[:method] = options.delete(:method) if options.has_key?(:method)
+ html_options[:authenticity_token] = options.delete(:authenticity_token)
- builder = options[:parent_builder] = instantiate_builder(object_name, object, options)
- fields_for = fields_for(object_name, object, options, &proc)
- default_options = builder.multipart? ? { multipart: true } : {}
- default_options.merge!(options.delete(:html))
+ builder = instantiate_builder(object_name, object, options)
+ output = capture(builder, &block)
+ html_options[:multipart] = builder.multipart?
- form_tag(options.delete(:url) || {}, default_options) { fields_for }
+ form_tag(options[:url] || {}, html_options) { output }
end
def apply_form_for_options!(record, object, options) #:nodoc:
@@ -460,8 +458,6 @@ module ActionView
# doesn't create the form tags themselves. This makes fields_for suitable
# for specifying additional model objects in the same form.
#
- # === Generic Examples
- #
# Although the usage and purpose of +field_for+ is similar to +form_for+'s,
# its method signature is slightly different. Like +form_for+, it yields
# a FormBuilder object associated with a particular model object to a block,
@@ -702,11 +698,14 @@ module ActionView
# <% end %>
# ...
# <% end %>
+ #
+ # Note that fields_for will automatically generate a hidden field
+ # to store the ID of the record. There are circumstances where this
+ # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt>
+ # to prevent fields_for from rendering it automatically.
def fields_for(record_name, record_object = nil, options = {}, &block)
builder = instantiate_builder(record_name, record_object, options)
- output = capture(builder, &block)
- output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
- output
+ capture(builder, &block)
end
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
@@ -772,8 +771,8 @@ module ActionView
# text_field(:post, :title, class: "create_input")
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
#
- # text_field(:session, :user, onchange: "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
- # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
+ # text_field(:session, :user, onchange: "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }")
+ # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }"/>
#
# text_field(:snippet, :code, size: 20, class: 'code_input')
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
@@ -827,13 +826,25 @@ module ActionView
#
# Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
#
+ # ==== Options
+ # * Creates standard HTML attributes for the tag.
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
+ # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
+ #
# ==== Examples
# file_field(:user, :avatar)
# # => <input type="file" id="user_avatar" name="user[avatar]" />
#
+ # file_field(:post, :image, :multiple => true)
+ # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
+ #
# file_field(:post, :attached, accept: 'text/html')
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
#
+ # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
+ # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
+ #
# file_field(:attachment, :file, class: 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
def file_field(object_name, method, options = {})
@@ -882,7 +893,7 @@ module ActionView
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
# any mass-assignment idiom like
#
- # @invoice.update_attributes(params[:invoice])
+ # @invoice.update(params[:invoice])
#
# wouldn't update the flag.
#
@@ -1159,12 +1170,15 @@ module ActionView
attr_accessor :object_name, :object, :options
- attr_reader :multipart, :parent_builder, :index
+ attr_reader :multipart, :index
alias :multipart? :multipart
def multipart=(multipart)
@multipart = multipart
- parent_builder.multipart = multipart if parent_builder
+
+ if parent_builder = @options[:parent_builder]
+ parent_builder.multipart = multipart
+ end
end
def self._to_partial_path
@@ -1186,7 +1200,6 @@ module ActionView
@nested_child_index = {}
@object_name, @object, @template, @options = object_name, object, template, options
- @parent_builder = options[:parent_builder]
@default_options = @options ? @options.slice(:index, :namespace) : {}
if @object_name.to_s.match(/\[\]$/)
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@@ -1211,11 +1224,260 @@ module ActionView
RUBY_EVAL
end
+ # Creates a scope around a specific model object like form_for, but
+ # doesn't create the form tags themselves. This makes fields_for suitable
+ # for specifying additional model objects in the same form.
+ #
+ # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
+ # its method signature is slightly different. Like +form_for+, it yields
+ # a FormBuilder object associated with a particular model object to a block,
+ # and within the block allows methods to be called on the builder to
+ # generate fields associated with the model object. Fields may reflect
+ # a model object in two ways - how they are named (hence how submitted
+ # values appear within the +params+ hash in the controller) and what
+ # default values are shown when the form the fields appear in is first
+ # displayed. In order for both of these features to be specified independently,
+ # both an object name (represented by either a symbol or string) and the
+ # object itself can be passed to the method separately -
+ #
+ # <%= form_for @person do |person_form| %>
+ # First name: <%= person_form.text_field :first_name %>
+ # Last name : <%= person_form.text_field :last_name %>
+ #
+ # <%= fields_for :permission, @person.permission do |permission_fields| %>
+ # Admin? : <%= permission_fields.check_box :admin %>
+ # <% end %>
+ #
+ # <%= f.submit %>
+ # <% end %>
+ #
+ # In this case, the checkbox field will be represented by an HTML +input+
+ # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
+ # value will appear in the controller as <tt>params[:permission][:admin]</tt>.
+ # If <tt>@person.permission</tt> is an existing record with an attribute
+ # +admin+, the initial state of the checkbox when first displayed will
+ # reflect the value of <tt>@person.permission.admin</tt>.
+ #
+ # Often this can be simplified by passing just the name of the model
+ # object to +fields_for+ -
+ #
+ # <%= fields_for :permission do |permission_fields| %>
+ # Admin?: <%= permission_fields.check_box :admin %>
+ # <% end %>
+ #
+ # ...in which case, if <tt>:permission</tt> also happens to be the name of an
+ # instance variable <tt>@permission</tt>, the initial state of the input
+ # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
+ #
+ # Alternatively, you can pass just the model object itself (if the first
+ # argument isn't a string or symbol +fields_for+ will realize that the
+ # name has been omitted) -
+ #
+ # <%= fields_for @person.permission do |permission_fields| %>
+ # Admin?: <%= permission_fields.check_box :admin %>
+ # <% end %>
+ #
+ # and +fields_for+ will derive the required name of the field from the
+ # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
+ # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
+ #
+ # Note: This also works for the methods in FormOptionHelper and
+ # DateHelper that are designed to work with an object as base, like
+ # FormOptionHelper#collection_select and DateHelper#datetime_select.
+ #
+ # === Nested Attributes Examples
+ #
+ # When the object belonging to the current scope has a nested attribute
+ # writer for a certain attribute, fields_for will yield a new scope
+ # for that attribute. This allows you to create forms that set or change
+ # the attributes of a parent object and its associations in one go.
+ #
+ # Nested attribute writers are normal setter methods named after an
+ # association. The most common way of defining these writers is either
+ # with +accepts_nested_attributes_for+ in a model definition or by
+ # defining a method with the proper name. For example: the attribute
+ # writer for the association <tt>:address</tt> is called
+ # <tt>address_attributes=</tt>.
+ #
+ # Whether a one-to-one or one-to-many style form builder will be yielded
+ # depends on whether the normal reader method returns a _single_ object
+ # or an _array_ of objects.
+ #
+ # ==== One-to-one
+ #
+ # Consider a Person class which returns a _single_ Address from the
+ # <tt>address</tt> reader method and responds to the
+ # <tt>address_attributes=</tt> writer method:
+ #
+ # class Person
+ # def address
+ # @address
+ # end
+ #
+ # def address_attributes=(attributes)
+ # # Process the attributes hash
+ # end
+ # end
+ #
+ # This model can now be used with a nested fields_for, like so:
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :address do |address_fields| %>
+ # Street : <%= address_fields.text_field :street %>
+ # Zip code: <%= address_fields.text_field :zip_code %>
+ # <% end %>
+ # ...
+ # <% end %>
+ #
+ # When address is already an association on a Person you can use
+ # +accepts_nested_attributes_for+ to define the writer method for you:
+ #
+ # class Person < ActiveRecord::Base
+ # has_one :address
+ # accepts_nested_attributes_for :address
+ # end
+ #
+ # If you want to destroy the associated model through the form, you have
+ # to enable it first using the <tt>:allow_destroy</tt> option for
+ # +accepts_nested_attributes_for+:
+ #
+ # class Person < ActiveRecord::Base
+ # has_one :address
+ # accepts_nested_attributes_for :address, allow_destroy: true
+ # end
+ #
+ # Now, when you use a form element with the <tt>_destroy</tt> parameter,
+ # with a value that evaluates to +true+, you will destroy the associated
+ # model (eg. 1, '1', true, or 'true'):
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :address do |address_fields| %>
+ # ...
+ # Delete: <%= address_fields.check_box :_destroy %>
+ # <% end %>
+ # ...
+ # <% end %>
+ #
+ # ==== One-to-many
+ #
+ # Consider a Person class which returns an _array_ of Project instances
+ # from the <tt>projects</tt> reader method and responds to the
+ # <tt>projects_attributes=</tt> writer method:
+ #
+ # class Person
+ # def projects
+ # [@project1, @project2]
+ # end
+ #
+ # def projects_attributes=(attributes)
+ # # Process the attributes hash
+ # end
+ # end
+ #
+ # Note that the <tt>projects_attributes=</tt> writer method is in fact
+ # required for fields_for to correctly identify <tt>:projects</tt> as a
+ # collection, and the correct indices to be set in the form markup.
+ #
+ # When projects is already an association on Person you can use
+ # +accepts_nested_attributes_for+ to define the writer method for you:
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :projects
+ # accepts_nested_attributes_for :projects
+ # end
+ #
+ # This model can now be used with a nested fields_for. The block given to
+ # the nested fields_for call will be repeated for each instance in the
+ # collection:
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :projects do |project_fields| %>
+ # <% if project_fields.object.active? %>
+ # Name: <%= project_fields.text_field :name %>
+ # <% end %>
+ # <% end %>
+ # ...
+ # <% end %>
+ #
+ # It's also possible to specify the instance to be used:
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <% @person.projects.each do |project| %>
+ # <% if project.active? %>
+ # <%= person_form.fields_for :projects, project do |project_fields| %>
+ # Name: <%= project_fields.text_field :name %>
+ # <% end %>
+ # <% end %>
+ # <% end %>
+ # ...
+ # <% end %>
+ #
+ # Or a collection to be used:
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
+ # Name: <%= project_fields.text_field :name %>
+ # <% end %>
+ # ...
+ # <% end %>
+ #
+ # When projects is already an association on Person you can use
+ # +accepts_nested_attributes_for+ to define the writer method for you:
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :projects
+ # accepts_nested_attributes_for :projects
+ # end
+ #
+ # If you want to destroy any of the associated models through the
+ # form, you have to enable it first using the <tt>:allow_destroy</tt>
+ # option for +accepts_nested_attributes_for+:
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :projects
+ # accepts_nested_attributes_for :projects, allow_destroy: true
+ # end
+ #
+ # This will allow you to specify which models to destroy in the
+ # attributes hash by adding a form element for the <tt>_destroy</tt>
+ # parameter with a value that evaluates to +true+
+ # (eg. 1, '1', true, or 'true'):
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :projects do |project_fields| %>
+ # Delete: <%= project_fields.check_box :_destroy %>
+ # <% end %>
+ # ...
+ # <% end %>
+ #
+ # When a collection is used you might want to know the index of each
+ # object into the array. For this purpose, the <tt>index</tt> method
+ # is available in the FormBuilder object.
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :projects do |project_fields| %>
+ # Project #<%= project_fields.index %>
+ # ...
+ # <% end %>
+ # ...
+ # <% end %>
+ #
+ # Note that fields_for will automatically generate a hidden field
+ # to store the ID of the record. There are circumstances where this
+ # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt>
+ # to prevent fields_for from rendering it automatically.
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
- fields_options[:parent_builder] = self
fields_options[:namespace] = options[:namespace]
+ fields_options[:parent_builder] = self
case record_name
when String, Symbol
@@ -1240,23 +1502,186 @@ module ActionView
@template.fields_for(record_name, record_object, fields_options, &block)
end
+ # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
+ # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
+ # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
+ # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
+ # target labels for radio_button tags (where the value is used in the ID of the input tag).
+ #
+ # ==== Examples
+ # label(:post, :title)
+ # # => <label for="post_title">Title</label>
+ #
+ # You can localize your labels based on model and attribute names.
+ # For example you can define the following in your locale (e.g. en.yml)
+ #
+ # helpers:
+ # label:
+ # post:
+ # body: "Write your entire text here"
+ #
+ # Which then will result in
+ #
+ # label(:post, :body)
+ # # => <label for="post_body">Write your entire text here</label>
+ #
+ # Localization can also be based purely on the translation of the attribute-name
+ # (if you are using ActiveRecord):
+ #
+ # activerecord:
+ # attributes:
+ # post:
+ # cost: "Total cost"
+ #
+ # label(:post, :cost)
+ # # => <label for="post_cost">Total cost</label>
+ #
+ # label(:post, :title, "A short title")
+ # # => <label for="post_title">A short title</label>
+ #
+ # label(:post, :title, "A short title", class: "title_label")
+ # # => <label for="post_title" class="title_label">A short title</label>
+ #
+ # label(:post, :privacy, "Public Post", value: "public")
+ # # => <label for="post_privacy_public">Public Post</label>
+ #
+ # label(:post, :terms) do
+ # 'Accept <a href="/terms">Terms</a>.'.html_safe
+ # end
def label(method, text = nil, options = {}, &block)
@template.label(@object_name, method, text, objectify_options(options), &block)
end
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
+ # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
+ #
+ # ==== Gotcha
+ #
+ # The HTML specification says unchecked check boxes are not successful, and
+ # thus web browsers do not send them. Unfortunately this introduces a gotcha:
+ # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
+ # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
+ # any mass-assignment idiom like
+ #
+ # @invoice.update(params[:invoice])
+ #
+ # wouldn't update the flag.
+ #
+ # To prevent this the helper generates an auxiliary hidden field before
+ # the very check box. The hidden field has the same name and its
+ # attributes mimic an unchecked check box.
+ #
+ # This way, the client either sends only the hidden field (representing
+ # the check box is unchecked), or both fields. Since the HTML specification
+ # says key/value pairs have to be sent in the same order they appear in the
+ # form, and parameters extraction gets the last occurrence of any repeated
+ # key in the query string, that works for ordinary forms.
+ #
+ # Unfortunately that workaround does not work when the check box goes
+ # within an array-like parameter, as in
+ #
+ # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %>
+ # <%= form.check_box :paid %>
+ # ...
+ # <% end %>
+ #
+ # because parameter name repetition is precisely what Rails seeks to distinguish
+ # the elements of the array. For each item with a checked check box you
+ # get an extra ghost item with only that attribute, assigned to "0".
+ #
+ # In that case it is preferable to either use +check_box_tag+ or to use
+ # hashes instead of arrays.
+ #
+ # # Let's say that @post.validated? is 1:
+ # check_box("post", "validated")
+ # # => <input name="post[validated]" type="hidden" value="0" />
+ # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
+ #
+ # # Let's say that @puppy.gooddog is "no":
+ # check_box("puppy", "gooddog", {}, "yes", "no")
+ # # => <input name="puppy[gooddog]" type="hidden" value="no" />
+ # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
+ #
+ # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
+ # # => <input name="eula[accepted]" type="hidden" value="no" />
+ # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
end
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
+ # radio button will be checked.
+ #
+ # To force the radio button to be checked pass <tt>checked: true</tt> in the
+ # +options+ hash. You may pass HTML options there as well.
+ #
+ # # Let's say that @post.category returns "rails":
+ # radio_button("post", "category", "rails")
+ # radio_button("post", "category", "java")
+ # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
+ # # <input type="radio" id="post_category_java" name="post[category]" value="java" />
+ #
+ # radio_button("user", "receive_newsletter", "yes")
+ # radio_button("user", "receive_newsletter", "no")
+ # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
+ # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(method, tag_value, options = {})
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
end
+ # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
+ # shown.
+ #
+ # ==== Examples
+ # hidden_field(:signup, :pass_confirm)
+ # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
+ #
+ # hidden_field(:post, :tag_list)
+ # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
+ #
+ # hidden_field(:user, :token)
+ # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
+ #
def hidden_field(method, options = {})
@emitted_hidden_id = true if method == :id
@template.hidden_field(@object_name, method, objectify_options(options))
end
+ # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
+ # shown.
+ #
+ # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
+ #
+ # ==== Options
+ # * Creates standard HTML attributes for the tag.
+ # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
+ # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
+ #
+ # ==== Examples
+ # file_field(:user, :avatar)
+ # # => <input type="file" id="user_avatar" name="user[avatar]" />
+ #
+ # file_field(:post, :image, :multiple => true)
+ # # => <input type="file" id="post_image" name="post[image]" multiple="true" />
+ #
+ # file_field(:post, :attached, accept: 'text/html')
+ # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
+ #
+ # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
+ # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
+ #
+ # file_field(:attachment, :file, class: 'file_input')
+ # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
def file_field(method, options = {})
self.multipart = true
@template.file_field(@object_name, method, objectify_options(options))
@@ -1393,13 +1818,17 @@ module ActionView
end
end
- def fields_for_nested_model(name, object, options, block)
+ def fields_for_nested_model(name, object, fields_options, block)
object = convert_to_model(object)
+ emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) {
+ options.fetch(:include_id, true)
+ }
- parent_include_id = self.options.fetch(:include_id, true)
- include_id = options.fetch(:include_id, parent_include_id)
- options[:hidden_field_id] = object.persisted? && include_id
- @template.fields_for(name, object, options, &block)
+ @template.fields_for(name, object, fields_options) do |f|
+ output = @template.capture(f, &block)
+ output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id?
+ output
+ end
end
def nested_child_index(name)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 46ebe60ec2..bcad05e033 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -2,18 +2,17 @@ require 'cgi'
require 'erb'
require 'action_view/helpers/form_helper'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/core_ext/array/wrap'
module ActionView
# = Action View Form Option Helpers
module Helpers
# Provides a number of methods for turning different kinds of containers into a set of option tags.
- # == Options
+ #
# The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
#
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
#
- # For example,
- #
# select("post", "category", Post::CATEGORIES, {include_blank: true})
#
# could become:
@@ -24,7 +23,7 @@ module ActionView
# <option>poem</option>
# </select>
#
- # Another common case is a select tag for an <tt>belongs_to</tt>-associated object.
+ # Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
#
# Example with @post.person_id => 2:
#
@@ -41,8 +40,6 @@ module ActionView
#
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
#
- # Example:
- #
# select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
#
# could become:
@@ -57,8 +54,6 @@ module ActionView
# Like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
# option to be in the +html_options+ parameter.
#
- # Example:
- #
# select("album[]", "genre", %w[rap rock country], {}, { index: nil })
#
# becomes:
@@ -71,8 +66,6 @@ module ActionView
#
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
#
- # Example:
- #
# select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
#
# could become:
@@ -86,8 +79,6 @@ module ActionView
#
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
#
- # Example:
- #
# collection_select(:post, :category_id, Category.all, :id, :name, {disabled: lambda{|category| category.archived? }})
#
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
@@ -139,7 +130,7 @@ module ActionView
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
# any mass-assignment idiom like
#
- # @user.update_attributes(params[:user])
+ # @user.update(params[:user])
#
# wouldn't update roles.
#
@@ -152,7 +143,8 @@ module ActionView
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
- # In case if you don't want the helper to generate this hidden field you can specify <tt>include_hidden: false</tt> option.
+ # In case if you don't want the helper to generate this hidden field you can specify
+ # <tt>include_hidden: false</tt> option.
#
def select(object, method, choices, options = {}, html_options = {})
Tags::Select.new(object, method, self, choices, options, html_options).render
@@ -170,9 +162,11 @@ module ActionView
# retrieve the value/text.
#
# Example object structure for use with this method:
+ #
# class Post < ActiveRecord::Base
# belongs_to :author
# end
+ #
# class Author < ActiveRecord::Base
# has_many :posts
# def name_with_initial
@@ -181,6 +175,7 @@ module ActionView
# end
#
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
+ #
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
#
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
@@ -213,23 +208,28 @@ module ActionView
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
#
# Example object structure for use with this method:
+ #
# class Continent < ActiveRecord::Base
# has_many :countries
# # attribs: id, name
# end
+ #
# class Country < ActiveRecord::Base
# belongs_to :continent
# # attribs: id, name, continent_id
# end
+ #
# class City < ActiveRecord::Base
# belongs_to :country
# # attribs: id, name, country_id
# end
#
# Sample usage:
+ #
# grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
#
# Possible output:
+ #
# <select name="city[country_id]">
# <optgroup label="Africa">
# <option value="1">South Africa</option>
@@ -284,57 +284,54 @@ module ActionView
# become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
# may also be an array of values to be selected when using a multiple select.
#
- # Examples (call, result):
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
- # # <option value="$">Dollar</option>
- # # <option value="DKK">Kroner</option>
+ # # => <option value="$">Dollar</option>
+ # # => <option value="DKK">Kroner</option>
#
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
- # # <option>VISA</option>
- # # <option selected="selected">MasterCard</option>
+ # # => <option>VISA</option>
+ # # => <option selected="selected">MasterCard</option>
#
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
- # # <option value="$20">Basic</option>
- # # <option value="$40" selected="selected">Plus</option>
+ # # => <option value="$20">Basic</option>
+ # # => <option value="$40" selected="selected">Plus</option>
#
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
- # # <option selected="selected">VISA</option>
- # # <option>MasterCard</option>
- # # <option selected="selected">Discover</option>
+ # # => <option selected="selected">VISA</option>
+ # # => <option>MasterCard</option>
+ # # => <option selected="selected">Discover</option>
#
# You can optionally provide html attributes as the last element of the array.
#
- # Examples:
# options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
- # # <option value="Denmark">Denmark</option>
- # # <option value="USA" class="bold" selected="selected">USA</option>
- # # <option value="Sweden" selected="selected">Sweden</option>
+ # # => <option value="Denmark">Denmark</option>
+ # # => <option value="USA" class="bold" selected="selected">USA</option>
+ # # => <option value="Sweden" selected="selected">Sweden</option>
#
# options_for_select([["Dollar", "$", {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]])
- # # <option value="$" class="bold">Dollar</option>
- # # <option value="DKK" onclick="alert('HI');">Kroner</option>
+ # # => <option value="$" class="bold">Dollar</option>
+ # # => <option value="DKK" onclick="alert('HI');">Kroner</option>
#
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
#
- # Examples:
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: "Super Platinum")
- # # <option value="Free">Free</option>
- # # <option value="Basic">Basic</option>
- # # <option value="Advanced">Advanced</option>
- # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # => <option value="Free">Free</option>
+ # # => <option value="Basic">Basic</option>
+ # # => <option value="Advanced">Advanced</option>
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: ["Advanced", "Super Platinum"])
- # # <option value="Free">Free</option>
- # # <option value="Basic">Basic</option>
- # # <option value="Advanced" disabled="disabled">Advanced</option>
- # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # => <option value="Free">Free</option>
+ # # => <option value="Basic">Basic</option>
+ # # => <option value="Advanced" disabled="disabled">Advanced</option>
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], selected: "Free", disabled: "Super Platinum")
- # # <option value="Free" selected="selected">Free</option>
- # # <option value="Basic">Basic</option>
- # # <option value="Advanced">Advanced</option>
- # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # => <option value="Free" selected="selected">Free</option>
+ # # => <option value="Basic">Basic</option>
+ # # => <option value="Advanced">Advanced</option>
+ # # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def options_for_select(container, selected = nil)
@@ -358,12 +355,12 @@ module ActionView
# Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
- # Example:
+ #
# options_from_collection_for_select(@people, 'id', 'name')
- # This will output the same HTML as if you did this:
- # <option value="#{person.id}">#{person.name}</option>
+ # # => <option value="#{person.id}">#{person.name}</option>
#
# This is more often than not used inside a #select_tag like this example:
+ #
# select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
#
# If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
@@ -412,10 +409,12 @@ module ActionView
# to be specified.
#
# Example object structure for use with this method:
+ #
# class Continent < ActiveRecord::Base
# has_many :countries
# # attribs: id, name
# end
+ #
# class Country < ActiveRecord::Base
# belongs_to :continent
# # attribs: id, name, continent_id
@@ -465,7 +464,6 @@ module ActionView
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
# * <tt>:divider</tt> - the divider for the options groups.
#
- # Sample usage (Array):
# grouped_options = [
# ['North America',
# [['United States','US'],'Canada']],
@@ -474,7 +472,6 @@ module ActionView
# ]
# grouped_options_for_select(grouped_options)
#
- # Sample usage (Hash):
# grouped_options = {
# 'North America' => [['United States','US'], 'Canada'],
# 'Europe' => ['Denmark','Germany','France']
@@ -482,17 +479,16 @@ module ActionView
# grouped_options_for_select(grouped_options)
#
# Possible output:
+ # <optgroup label="North America">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
# <optgroup label="Europe">
# <option value="Denmark">Denmark</option>
# <option value="Germany">Germany</option>
# <option value="France">France</option>
# </optgroup>
- # <optgroup label="North America">
- # <option value="US">United States</option>
- # <option value="Canada">Canada</option>
- # </optgroup>
#
- # Sample usage (divider):
# grouped_options = [
# [['United States','US'], 'Canada'],
# ['Denmark','Germany','France']
@@ -530,8 +526,6 @@ module ActionView
body.safe_concat content_tag(:option, prompt_text(prompt), :value => "")
end
- grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
-
grouped_options.each do |container|
if divider
label = divider
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index e298751062..479739bebd 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -184,7 +184,7 @@ module ActionView
# # => <label for="name">Name</label>
#
# label_tag 'name', 'Your name'
- # # => <label for="name">Your Name</label>
+ # # => <label for="name">Your name</label>
#
# label_tag 'name', nil, class: 'small_label'
# # => <label for="name" class="small_label">Name</label>
@@ -233,6 +233,8 @@ module ActionView
# ==== Options
# * Creates standard HTML attributes for the tag.
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
+ # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
+ # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
#
# ==== Examples
# file_field_tag 'attachment'
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 1a99fc7091..cfdd7c77d8 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -18,7 +18,8 @@ module ActionView
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
- # Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
+ # Also available through the alias j(). This is particularly helpful in JavaScript
+ # responses, like:
#
# $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
@@ -43,12 +44,14 @@ module ActionView
# </script>
#
# +html_options+ may be a hash of attributes for the <tt>\<script></tt>
- # tag. Example:
+ # tag.
+ #
# javascript_tag "alert('All is good')", defer: 'defer'
# # => <script defer="defer">alert('All is good')</script>
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
+ #
# <%= javascript_tag defer: 'defer' do -%>
# alert('All is good')
# <% end -%>
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 82340171af..9e1be65b1a 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -28,8 +28,6 @@ module ActionView
# 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 "-").
@@ -40,21 +38,18 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
- # ==== 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("1234a567", raise: true) # => InvalidNumberError
- #
- # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
- # # => +1.123.555.1234 x 1343
+ # 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("1234a567", raise: true) # => InvalidNumberError
+ #
+ # 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
@@ -66,8 +61,6 @@ module ActionView
# 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
@@ -89,22 +82,20 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
- # ==== 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) # => $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("123a456", raise: true) # => InvalidNumberError
+ # number_to_currency("123a456", raise: true) # => InvalidNumberError
#
- # 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;
+ # 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 = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
@@ -117,7 +108,6 @@ module ActionView
# 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).
@@ -138,18 +128,16 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
- # ==== 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 %
#
- # 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 %
- #
- # number_to_percentage("98a", raise: true) # => InvalidNumberError
+ # number_to_percentage("98a", raise: true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
@@ -163,8 +151,6 @@ module ActionView
# (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
@@ -174,20 +160,18 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
- # ==== Examples
- #
- # number_with_delimiter(12345678) # => 12,345,678
- # number_with_delimiter("123456") # => 123,456
- # number_with_delimiter(12345678.05) # => 12,345,678.05
- # number_with_delimiter(12345678, delimiter: ".") # => 12.345.678
- # number_with_delimiter(12345678, delimiter: ",") # => 12,345,678
- # number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05
- # number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05
- # number_with_delimiter("112a") # => 112a
- # number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
- # # => 98 765 432,98
- #
- # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
+ # number_with_delimiter(12345678) # => 12,345,678
+ # number_with_delimiter("123456") # => 123,456
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
+ # number_with_delimiter(12345678, delimiter: ".") # => 12.345.678
+ # number_with_delimiter(12345678, delimiter: ",") # => 12,345,678
+ # number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05
+ # number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05
+ # number_with_delimiter("112a") # => 112a
+ # number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
+ # # => 98 765 432,98
+ #
+ # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
@@ -201,8 +185,6 @@ module ActionView
# +: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
@@ -220,23 +202,21 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
- # ==== Examples
- #
- # number_with_precision(111.2345) # => 111.235
- # number_with_precision(111.2345, precision: 2) # => 111.23
- # number_with_precision(13, precision: 5) # => 13.00000
- # number_with_precision(389.32314, precision: 0) # => 389
- # number_with_precision(111.2345, significant: true) # => 111
- # number_with_precision(111.2345, precision: 1, significant: true) # => 100
- # number_with_precision(13, precision: 5, significant: true) # => 13.000
- # number_with_precision(111.234, locale: :fr) # => 111,234
- #
- # number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true)
- # # => 13
- #
- # number_with_precision(389.32314, precision: 4, significant: true) # => 389.3
- # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.')
- # # => 1.111,23
+ # number_with_precision(111.2345) # => 111.235
+ # number_with_precision(111.2345, precision: 2) # => 111.23
+ # number_with_precision(13, precision: 5) # => 13.00000
+ # number_with_precision(389.32314, precision: 0) # => 389
+ # number_with_precision(111.2345, significant: true) # => 111
+ # number_with_precision(111.2345, precision: 1, significant: true) # => 100
+ # number_with_precision(13, precision: 5, significant: true) # => 13.000
+ # number_with_precision(111.234, locale: :fr) # => 111,234
+ #
+ # number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true)
+ # # => 13
+ #
+ # number_with_precision(389.32314, precision: 4, significant: true) # => 389.3
+ # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.')
+ # # => 1.111,23
def number_with_precision(number, options = {})
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
@@ -245,7 +225,6 @@ module ActionView
}
end
-
# 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
@@ -254,8 +233,6 @@ module ActionView
# 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
@@ -275,24 +252,23 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
- # ==== 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
+ # 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"
+ #
+ # 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 = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
@@ -348,29 +324,27 @@ module ActionView
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
- # ==== 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,
+ # 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"
+ # 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"
+ # number_to_human(12345012345, significant_digits: 6) # => "12.345 Billion"
+ # number_to_human(500000000, precision: 5) # => "500 Million"
#
# ==== Custom Unit Quantifiers
#
@@ -392,12 +366,12 @@ module ActionView
#
# 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"
+ # 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 = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb
index 2e7e9dc50c..60a4478c26 100644
--- a/actionpack/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionpack/lib/action_view/helpers/output_safety_helper.rb
@@ -11,7 +11,8 @@ module ActionView #:nodoc:
#
# For example:
#
- # <%=raw @user.name %>
+ # raw @user.name
+ # # => 'Jimmy <alert>Tables</alert>'
def raw(stringish)
stringish.to_s.html_safe
end
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 33194250b7..271a194913 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -65,8 +65,8 @@ module ActionView
#
# produces:
#
- # <tr id="person_123" class="person">...</tr>
- # <tr id="person_124" class="person">...</tr>
+ # <tr id="person_123" class="person">...</tr>
+ # <tr id="person_124" class="person">...</tr>
#
# content_tag_for also accepts a hash of options, which will be converted to
# additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
@@ -95,7 +95,11 @@ module ActionView
options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip
options[:id] = dom_id(record, prefix)
- content_tag(tag_name, capture(record, &block), options)
+ if block_given?
+ content_tag(tag_name, capture(record, &block), options)
+ else
+ content_tag(tag_name, "", options)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index e6f61d269c..e5cb843670 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -3,7 +3,7 @@ require 'action_view/vendor/html-scanner'
module ActionView
# = Action View Sanitize Helpers
- module Helpers #:nodoc:
+ module Helpers
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
index 45f0bc3d7b..d27df45b5a 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -21,7 +21,7 @@ module ActionView
if block_given?
yield builder
else
- builder.check_box + builder.label
+ render_component(builder)
end
end
@@ -31,6 +31,12 @@ module ActionView
rendered_collection + hidden
end
+
+ private
+
+ def render_component(builder)
+ builder.check_box + builder.label
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
index 4e33e79a36..e92a318c73 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -44,7 +44,8 @@ module ActionView
html_options = @html_options.dup
[:checked, :selected, :disabled].each do |option|
- next unless current_value = @options[option]
+ current_value = @options[option]
+ next if current_value.nil?
accept = if current_value.respond_to?(:call)
current_value.call(item)
diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
index ba2035f074..81f2ecb2b3 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -20,10 +20,16 @@ module ActionView
if block_given?
yield builder
else
- builder.radio_button + builder.label
+ render_component(builder)
end
end
end
+
+ private
+
+ def render_component(builder)
+ builder.radio_button + builder.label
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb
index 5d706087b0..6c400f85cb 100644
--- a/actionpack/lib/action_view/helpers/tags/date_select.rb
+++ b/actionpack/lib/action_view/helpers/tags/date_select.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/time/calculations'
+
module ActionView
module Helpers
module Tags
@@ -58,7 +60,7 @@ module ActionView
default[key] ||= time.send(key)
end
- Time.utc_time(
+ Time.utc(
default[:year], default[:month], default[:day],
default[:hour], default[:min], default[:sec]
)
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 26d2142df9..2e124cf085 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -112,12 +112,12 @@ module ActionView
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
# # => 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
else
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
end.html_safe
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index fd671c9c07..aeee662071 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -415,40 +415,26 @@ module ActionView
# also used as the name of the link unless +name+ is specified. Additional
# HTML attributes for the link can be passed in +html_options+.
#
- # +mail_to+ has several methods for hindering email harvesters and customizing
- # the email itself by passing special keys to +html_options+.
+ # +mail_to+ has several methods for customizing the email itself by
+ # passing special keys to +html_options+.
#
# ==== Options
- # * <tt>:encode</tt> - This key will accept the strings "javascript" or "hex".
- # Passing "javascript" will dynamically create and encode the mailto link then
- # eval it into the DOM of the page. This method will not show the link on
- # the page if the user has JavaScript disabled. Passing "hex" will hex
- # encode the +email_address+ before outputting the mailto link.
- # * <tt>:replace_at</tt> - When the link +name+ isn't provided, the
- # +email_address+ is used for the link label. You can use this option to
- # obfuscate the +email_address+ by substituting the @ sign with the string
- # given as the value.
- # * <tt>:replace_dot</tt> - When the link +name+ isn't provided, the
- # +email_address+ is used for the link label. You can use this option to
- # obfuscate the +email_address+ by substituting the . in the email with the
- # string given as the value.
# * <tt>:subject</tt> - Preset the subject line of the email.
# * <tt>:body</tt> - Preset the body of the email.
# * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
+ # ==== Obfuscation
+ # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
+ # in order to hinder email harvesters. To take advantage of these options,
+ # install the +actionview-encoded_mail_to+ gem.
+ #
# ==== Examples
# mail_to "me@domain.com"
# # => <a href="mailto:me@domain.com">me@domain.com</a>
#
- # mail_to "me@domain.com", "My email", encode: "javascript"
- # # => <script>eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
- #
- # mail_to "me@domain.com", "My email", encode: "hex"
- # # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
- #
- # mail_to "me@domain.com", nil, replace_at: "_at_", replace_dot: "_dot_", class: "email"
- # # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a>
+ # mail_to "me@domain.com", "My email"
+ # # => <a href="mailto:me@domain.com">My email</a>
#
# mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
# subject: "This is an example email"
@@ -456,49 +442,21 @@ module ActionView
def mail_to(email_address, name = nil, html_options = {})
email_address = ERB::Util.html_escape(email_address)
- html_options = html_options.stringify_keys
- encode = html_options.delete("encode").to_s
+ html_options.stringify_keys!
extras = %w{ cc bcc body subject }.map { |item|
option = html_options.delete(item) || next
"#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
-
- email_address_obfuscated = email_address.to_str
- email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at")
- email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot")
- case encode
- when "javascript"
- string = ''
- html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))
- html = escape_javascript(html.to_str)
- "document.write('#{html}');".each_byte do |c|
- string << sprintf("%%%x", c)
- end
- "<script>eval(decodeURIComponent('#{string}'))</script>".html_safe
- when "hex"
- email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
- sprintf("&#%d;", c)
- }.join
-
- string = 'mailto:'.unpack('C*').map { |c|
- sprintf("&#%d;", c)
- }.join + email_address.unpack('C*').map { |c|
- char = c.chr
- char =~ /\w/ ? sprintf("%%%x", c) : char
- }.join
-
- content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe)
- else
- content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
- end
+
+ content_tag "a", name || email_address.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
end
# True if the current request URI was generated by the given +options+.
#
# ==== Examples
- # Let's say we're in the <tt>/shop/checkout?order=desc</tt> action.
+ # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc</tt> action.
#
# current_page?(action: 'process')
# # => false
@@ -515,7 +473,13 @@ module ActionView
# current_page?(controller: 'library', action: 'checkout')
# # => false
#
- # Let's say we're in the <tt>/shop/checkout?order=desc&page=1</tt> action.
+ # current_page?('http://www.example.com/shop/checkout')
+ # # => true
+ #
+ # current_page?('/shop/checkout')
+ # # => true
+ #
+ # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
#
# current_page?(action: 'process')
# # => false
@@ -538,7 +502,7 @@ module ActionView
# current_page?(controller: 'library', action: 'checkout')
# # => false
#
- # Let's say we're in the <tt>/products</tt> action with method POST in case of invalid product.
+ # Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
#
# current_page?(controller: 'product', action: 'index')
# # => false
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 76f4dea7b8..4e4816d983 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -1,3 +1,4 @@
+require 'thread_safe'
require 'active_support/core_ext/module/remove_method'
module ActionView
@@ -51,7 +52,7 @@ module ActionView
alias :object_hash :hash
attr_reader :hash
- @details_keys = Hash.new
+ @details_keys = ThreadSafe::Cache.new
def self.get(details)
@details_keys[details] ||= new
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 3875d88a9f..e80e0ed9b0 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -3,7 +3,7 @@ require "rails"
module ActionView
# = Action View Railtie
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie # :nodoc:
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.embed_authenticity_token_in_remote_forms = false
diff --git a/actionpack/lib/action_view/record_identifier.rb b/actionpack/lib/action_view/record_identifier.rb
index 2953654972..63f645431a 100644
--- a/actionpack/lib/action_view/record_identifier.rb
+++ b/actionpack/lib/action_view/record_identifier.rb
@@ -17,7 +17,7 @@ module ActionView
# # controller
# def update
# post = Post.find(params[:id])
- # post.update_attributes(params[:post])
+ # post.update(params[:post])
#
# redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 36d557e1a3..37f93a13fc 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -1,3 +1,4 @@
+require 'thread_safe'
module ActionView
# = Action View Partials
@@ -248,7 +249,9 @@ module ActionView
# <%- end -%>
# <% end %>
class PartialRenderer < AbstractRenderer
- PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
+ PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
+ h[k] = ThreadSafe::Cache.new
+ end
def initialize(*)
super
@@ -294,7 +297,7 @@ module ActionView
object, as = @object, @variable
if !block && (layout = @options[:layout])
- layout = find_template(layout, @template_keys)
+ layout = find_template(layout.to_s, @template_keys)
end
object ||= locals[as]
diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb
index bf1b5a7d22..30a0c4be70 100644
--- a/actionpack/lib/action_view/renderer/renderer.rb
+++ b/actionpack/lib/action_view/renderer/renderer.rb
@@ -33,22 +33,12 @@ module ActionView
# Direct accessor to template rendering.
def render_template(context, options) #:nodoc:
- _template_renderer.render(context, options)
+ TemplateRenderer.new(@lookup_context).render(context, options)
end
# Direct access to partial rendering.
def render_partial(context, options, &block) #:nodoc:
- _partial_renderer.render(context, options, block)
- end
-
- private
-
- def _template_renderer #:nodoc:
- @_template_renderer ||= TemplateRenderer.new(@lookup_context)
- end
-
- def _partial_renderer #:nodoc:
- @_partial_renderer ||= PartialRenderer.new(@lookup_context)
+ PartialRenderer.new(@lookup_context).render(context, options, block)
end
end
end
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index 2a5ea5a711..4d5c5db80c 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -41,7 +41,7 @@ module ActionView
# Renders the given template. A string representing the layout can be
# supplied as well.
- def render_template(template, layout_name = nil, locals = {}) #:nodoc:
+ def render_template(template, layout_name = nil, locals = nil) #:nodoc:
view, locals = @view, locals || {}
render_with_layout(layout_name, locals) do |layout|
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index aefc42be48..b927c69260 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/deprecation'
require 'thread'
module ActionView
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index e00056781d..b479f991bc 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -78,7 +78,7 @@ module ActionView
end
end
- def source_extract(indentation = 0)
+ def source_extract(indentation = 0, output = :console)
return unless num = line_number
num = num.to_i
@@ -88,13 +88,9 @@ module ActionView
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
indent = end_on_line.to_s.size + indentation
- line_counter = start_on_line
return unless source_code = source_code[start_on_line..end_on_line]
- source_code.sum do |line|
- line_counter += 1
- "%#{indent}s: %s\n" % [line_counter, line]
- end
+ formatted_code_for(source_code, start_on_line, indent, output)
end
def sub_template_of(template_path)
@@ -123,6 +119,18 @@ module ActionView
'in '
end + file_name
end
+
+ def formatted_code_for(source_code, line_counter, indent, output)
+ start_value = (output == :html) ? {} : ""
+ source_code.inject(start_value) do |result, line|
+ line_counter += 1
+ if output == :html
+ result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
+ else
+ result << "%#{indent}s: %s\n" % [line_counter, line]
+ end
+ end
+ end
end
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 731d8f9dab..afbbece90f 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -14,6 +14,17 @@ module ActionView
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
end
+ # Erubis toggles <%= and <%== behavior when escaping is enabled.
+ # We override to always treat <%== as escaped.
+ def add_expr(src, code, indicator)
+ case indicator
+ when '=='
+ add_expr_escaped(src, code)
+ else
+ super
+ end
+ end
+
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index fc77c1485d..1a1083bf00 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -3,7 +3,7 @@ require "active_support/core_ext/class"
require "active_support/core_ext/class/attribute_accessors"
require "action_view/template"
require "thread"
-require "mutex_m"
+require "thread_safe"
module ActionView
# = Action View Resolver
@@ -35,52 +35,51 @@ module ActionView
# Threadsafe template cache
class Cache #:nodoc:
- class CacheEntry
- include Mutex_m
-
- attr_accessor :templates
+ class SmallCache < ThreadSafe::Cache
+ def initialize(options = {})
+ super(options.merge(:initial_capacity => 2))
+ end
end
+ # preallocate all the default blocks for performance/memory consumption reasons
+ PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new}
+ PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)}
+ NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
+ KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
+
+ # usually a majority of template look ups return nothing, use this canonical preallocated array to safe memory
+ NO_TEMPLATES = [].freeze
+
def initialize
- @data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2|
- h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } }
- @mutex = Mutex.new
+ @data = SmallCache.new(&KEY_BLOCK)
end
# Cache the templates returned by the block
def cache(key, name, prefix, partial, locals)
- cache_entry = nil
-
- # first obtain a lock on the main data structure to create the cache entry
- @mutex.synchronize do
- cache_entry = @data[key][name][prefix][partial][locals] ||= CacheEntry.new
- end
-
- # then to avoid a long lasting global lock, obtain a more granular lock
- # on the CacheEntry itself
- cache_entry.synchronize do
- if Resolver.caching?
- cache_entry.templates ||= yield
+ if Resolver.caching?
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
+ else
+ fresh_templates = yield
+ cached_templates = @data[key][name][prefix][partial][locals]
+
+ if templates_have_changed?(cached_templates, fresh_templates)
+ @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
else
- fresh_templates = yield
-
- if templates_have_changed?(cache_entry.templates, fresh_templates)
- cache_entry.templates = fresh_templates
- else
- cache_entry.templates ||= []
- end
+ cached_templates || NO_TEMPLATES
end
end
end
def clear
- @mutex.synchronize do
- @data.clear
- end
+ @data.clear
end
private
+ def canonical_no_templates(templates)
+ templates.empty? ? NO_TEMPLATES : templates
+ end
+
def templates_have_changed?(cached_templates, fresh_templates)
# if either the old or new template list is empty, we don't need to (and can't)
# compare modification times, and instead just check whether the lists are different
@@ -119,7 +118,7 @@ module ActionView
private
- delegate :caching?, :to => "self.class"
+ delegate :caching?, to: :class
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
diff --git a/actionpack/lib/action_view/template/types.rb b/actionpack/lib/action_view/template/types.rb
index 7611c9e708..db77cb5d19 100644
--- a/actionpack/lib/action_view/template/types.rb
+++ b/actionpack/lib/action_view/template/types.rb
@@ -1,6 +1,5 @@
require 'set'
require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/object/blank'
module ActionView
class Template
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index a548b44780..4479da5bc4 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -30,9 +30,6 @@ module ActionView
end
end
- # Use AV::TestCase for the base class for helpers and views
- register_spec_type(/(Helper|View)( ?Test)?\z/i, self)
-
module Behavior
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_view/vendor/html-scanner.rb b/actionpack/lib/action_view/vendor/html-scanner.rb
index 879b31e60e..775b827529 100644
--- a/actionpack/lib/action_view/vendor/html-scanner.rb
+++ b/actionpack/lib/action_view/vendor/html-scanner.rb
@@ -1,4 +1,4 @@
-$LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner"
+$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner"
module HTML
extend ActiveSupport::Autoload
diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb
index 62f82a4c7a..eb9143c8f6 100644
--- a/actionpack/test/abstract/abstract_controller_test.rb
+++ b/actionpack/test/abstract/abstract_controller_test.rb
@@ -157,13 +157,11 @@ module AbstractController
private
def self.layout(formats)
+ find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"])
+ rescue ActionView::MissingTemplate
begin
- find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"])
+ find_template("application", {:formats => formats}, :_prefixes => ["layouts"])
rescue ActionView::MissingTemplate
- begin
- find_template("application", {:formats => formats}, :_prefixes => ["layouts"])
- rescue ActionView::MissingTemplate
- end
end
end
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 5d1a703c55..1090af3060 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -28,9 +28,9 @@ module AbstractController
end
class Callback2 < ControllerWithCallbacks
- before_filter :first
- after_filter :second
- around_filter :aroundz
+ before_action :first
+ after_action :second
+ around_action :aroundz
def first
@text = "Hello world"
@@ -53,7 +53,7 @@ module AbstractController
end
class Callback2Overwrite < Callback2
- before_filter :first, :except => :index
+ before_action :first, except: :index
end
class TestCallbacks2 < ActiveSupport::TestCase
@@ -61,22 +61,22 @@ module AbstractController
@controller = Callback2.new
end
- test "before_filter works" do
+ test "before_action works" do
@controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
- test "after_filter works" do
+ test "after_action works" do
@controller.process(:index)
assert_equal "Goodbye", @controller.instance_variable_get("@second")
end
- test "around_filter works" do
+ test "around_action works" do
@controller.process(:index)
assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz")
end
- test "before_filter with overwritten condition" do
+ test "before_action with overwritten condition" do
@controller = Callback2Overwrite.new
@controller.process(:index)
assert_equal "", @controller.response_body
@@ -84,11 +84,11 @@ module AbstractController
end
class Callback3 < ControllerWithCallbacks
- before_filter do |c|
+ before_action do |c|
c.instance_variable_set("@text", "Hello world")
end
- after_filter do |c|
+ after_action do |c|
c.instance_variable_set("@second", "Goodbye")
end
@@ -102,20 +102,20 @@ module AbstractController
@controller = Callback3.new
end
- test "before_filter works with procs" do
+ test "before_action works with procs" do
@controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
- test "after_filter works with procs" do
+ test "after_action works with procs" do
@controller.process(:index)
assert_equal "Goodbye", @controller.instance_variable_get("@second")
end
end
class CallbacksWithConditions < ControllerWithCallbacks
- before_filter :list, :only => :index
- before_filter :authenticate, :except => :index
+ before_action :list, :only => :index
+ before_action :authenticate, :except => :index
def index
self.response_body = @list.join(", ")
@@ -141,25 +141,25 @@ module AbstractController
@controller = CallbacksWithConditions.new
end
- test "when :only is specified, a before filter is triggered on that action" do
+ test "when :only is specified, a before action is triggered on that action" do
@controller.process(:index)
assert_equal "Hello, World", @controller.response_body
end
- test "when :only is specified, a before filter is not triggered on other actions" do
+ test "when :only is specified, a before action is not triggered on other actions" do
@controller.process(:sekrit_data)
assert_equal "true", @controller.response_body
end
- test "when :except is specified, an after filter is not triggered on that action" do
+ test "when :except is specified, an after action is not triggered on that action" do
@controller.process(:index)
assert !@controller.instance_variable_defined?("@authenticated")
end
end
class CallbacksWithArrayConditions < ControllerWithCallbacks
- before_filter :list, :only => [:index, :listy]
- before_filter :authenticate, :except => [:index, :listy]
+ before_action :list, only: [:index, :listy]
+ before_action :authenticate, except: [:index, :listy]
def index
self.response_body = @list.join(", ")
@@ -185,24 +185,24 @@ module AbstractController
@controller = CallbacksWithArrayConditions.new
end
- test "when :only is specified with an array, a before filter is triggered on that action" do
+ test "when :only is specified with an array, a before action is triggered on that action" do
@controller.process(:index)
assert_equal "Hello, World", @controller.response_body
end
- test "when :only is specified with an array, a before filter is not triggered on other actions" do
+ test "when :only is specified with an array, a before action is not triggered on other actions" do
@controller.process(:sekrit_data)
assert_equal "true", @controller.response_body
end
- test "when :except is specified with an array, an after filter is not triggered on that action" do
+ test "when :except is specified with an array, an after action is not triggered on that action" do
@controller.process(:index)
assert !@controller.instance_variable_defined?("@authenticated")
end
end
class ChangedConditions < Callback2
- before_filter :first, :only => :index
+ before_action :first, :only => :index
def not_index
@text ||= nil
@@ -227,7 +227,7 @@ module AbstractController
end
class SetsResponseBody < ControllerWithCallbacks
- before_filter :set_body
+ before_action :set_body
def index
self.response_body = "Fail"
@@ -266,6 +266,50 @@ module AbstractController
end
end
+ class AliasedCallbacks < ControllerWithCallbacks
+ before_filter :first
+ after_filter :second
+ around_filter :aroundz
+
+ def first
+ @text = "Hello world"
+ end
+
+ def second
+ @second = "Goodbye"
+ end
+
+ def aroundz
+ @aroundz = "FIRST"
+ yield
+ @aroundz << "SECOND"
+ end
+ def index
+ @text ||= nil
+ self.response_body = @text.to_s
+ end
+ end
+
+ class TestAliasedCallbacks < ActiveSupport::TestCase
+ def setup
+ @controller = AliasedCallbacks.new
+ end
+
+ test "before_filter works" do
+ @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
+ end
+
+ test "after_filter works" do
+ @controller.process(:index)
+ assert_equal "Goodbye", @controller.instance_variable_get("@second")
+ end
+
+ test "around_filter works" do
+ @controller.process(:index)
+ assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz")
+ end
+ end
end
end
diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb
index e79008fa9d..7960e5b55b 100644
--- a/actionpack/test/abstract/helper_test.rb
+++ b/actionpack/test/abstract/helper_test.rb
@@ -69,12 +69,10 @@ module AbstractController
end
def test_declare_missing_helper
- begin
- AbstractHelpers.helper :missing
- flunk "should have raised an exception"
- rescue LoadError => e
- assert_equal "helpers/missing_helper.rb", e.path
- end
+ AbstractHelpers.helper :missing
+ flunk "should have raised an exception"
+ rescue LoadError => e
+ assert_equal "helpers/missing_helper.rb", e.path
end
def test_helpers_with_module_through_block
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 4f5b2895c9..bbcd289886 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -15,7 +15,7 @@ silence_warnings do
Encoding.default_external = "UTF-8"
end
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'abstract_controller'
require 'action_controller'
require 'action_view'
@@ -25,7 +25,6 @@ require 'active_support/dependencies'
require 'active_model'
require 'active_record'
require 'action_controller/caching'
-require 'action_controller/caching/sweeping'
require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb
index 4dd7406798..95fbb112c0 100644
--- a/actionpack/test/active_record_unit.rb
+++ b/actionpack/test/active_record_unit.rb
@@ -45,19 +45,11 @@ class ActiveRecordTestConnector
def setup_connection
if Object.const_defined?(:ActiveRecord)
defaults = { :database => ':memory:' }
- begin
- adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'
- options = defaults.merge :adapter => adapter, :timeout => 500
- ActiveRecord::Base.establish_connection(options)
- ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
- ActiveRecord::Base.connection
- rescue Exception # errors from establishing a connection
- $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
- options = defaults.merge :adapter => 'sqlite'
- ActiveRecord::Base.establish_connection(options)
- ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options }
- ActiveRecord::Base.connection
- end
+ adapter = defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3'
+ options = defaults.merge :adapter => adapter, :timeout => 500
+ ActiveRecord::Base.establish_connection(options)
+ ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
+ ActiveRecord::Base.connection
Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
else
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index ca542eb7e2..5d727b3811 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -259,7 +259,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_flash_exist
process :flash_me
assert flash.any?
- assert_present flash['hello']
+ assert flash['hello'].present?
end
def test_flash_does_not_exist
@@ -430,6 +430,12 @@ end
class AssertTemplateTest < ActionController::TestCase
tests ActionPackAssertionsController
+ def test_with_invalid_hash_keys_raises_argument_error
+ assert_raise(ArgumentError) do
+ assert_template foo: "bar"
+ end
+ end
+
def test_with_partial
get :partial
assert_template :partial => '_partial'
@@ -447,6 +453,20 @@ class AssertTemplateTest < ActionController::TestCase
end
end
+ def test_with_empty_string_fails_when_template_rendered
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template ""
+ end
+ end
+
+ def test_with_empty_string_fails_when_no_template_rendered
+ get :nothing
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template ""
+ end
+ end
+
def test_passes_with_correct_string
get :hello_world
assert_template 'hello_world'
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index 38598a520c..3d667f0a2f 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -10,16 +10,6 @@ require 'controller/fake_controllers'
require 'action_mailer'
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
-class SynchronousQueue < Queue
- def push(job)
- job.run
- end
- alias << push
- alias enq push
-end
-
-ActionMailer::Base.queue = SynchronousQueue.new
-
class AssertSelectTest < ActionController::TestCase
Assertion = ActiveSupport::TestCase::Assertion
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 65c18dfb64..2428cd7433 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -164,6 +164,9 @@ class FunctionalCachingController < CachingController
format.xml
end
end
+
+ def fragment_cached_without_digest
+ end
end
class FunctionalFragmentCachingTest < ActionController::TestCase
@@ -200,6 +203,15 @@ CACHED
@store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial", "html")}"))
end
+ def test_skipping_fragment_cache_digesting
+ get :fragment_cached_without_digest, :format => "html"
+ assert_response :success
+ expected_body = "<body>\n<p>ERB</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+ assert_equal "<p>ERB</p>", @store.read("views/nodigest")
+ end
+
def test_render_inline_before_fragment_caching
get :inline_fragment_cached
assert_response :success
diff --git a/actionpack/test/controller/default_url_options_with_filter_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb
index 9a9ab17fee..656fd0431e 100644
--- a/actionpack/test/controller/default_url_options_with_filter_test.rb
+++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb
@@ -1,10 +1,10 @@
require 'abstract_unit'
-class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base
+class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
- before_filter { I18n.locale = params[:locale] }
- after_filter { I18n.locale = "en" }
+ before_action { I18n.locale = params[:locale] }
+ after_action { I18n.locale = "en" }
def target
render :text => "final response"
@@ -19,11 +19,11 @@ class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base
end
end
-class ControllerWithBeforeFilterAndDefaultUrlOptionsTest < ActionController::TestCase
+class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::TestCase
# This test has its roots in issue #1872
test "should redirect with correct locale :de" do
get :redirect, :locale => "de"
- assert_redirected_to "/controller_with_before_filter_and_default_url_options/target?locale=de"
+ assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de"
end
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index d203601771..3b79161ad3 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -450,11 +450,9 @@ class FilterTest < ActionController::TestCase
class RescuingAroundFilterWithBlock
def around(controller)
- begin
- yield
- rescue ErrorToRescue => ex
- controller.__send__ :render, :text => "I rescued this: #{ex.inspect}"
- end
+ yield
+ rescue ErrorToRescue => ex
+ controller.__send__ :render, :text => "I rescued this: #{ex.inspect}"
end
end
@@ -499,18 +497,6 @@ class FilterTest < ActionController::TestCase
end
- class ::AppSweeper < ActionController::Caching::Sweeper; end
- class SweeperTestController < ActionController::Base
- cache_sweeper :app_sweeper
- def show
- render :text => 'hello world'
- end
-
- def error
- raise StandardError.new
- end
- end
-
class ImplicitActionsController < ActionController::Base
before_filter :find_only, :only => :edit
before_filter :find_except, :except => :edit
@@ -526,35 +512,6 @@ class FilterTest < ActionController::TestCase
end
end
- def test_sweeper_should_not_ignore_no_method_error
- sweeper = ActionController::Caching::Sweeper.send(:new)
- assert_raise NoMethodError do
- sweeper.send_not_defined
- end
- end
-
- def test_sweeper_should_not_block_rendering
- response = test_process(SweeperTestController)
- assert_equal 'hello world', response.body
- end
-
- def test_sweeper_should_clean_up_if_exception_is_raised
- assert_raise StandardError do
- test_process(SweeperTestController, 'error')
- end
- assert_nil AppSweeper.instance.controller
- end
-
- def test_before_method_of_sweeper_should_always_return_true
- sweeper = ActionController::Caching::Sweeper.send(:new)
- assert sweeper.before(TestController.new)
- end
-
- def test_after_method_of_sweeper_should_always_return_nil
- sweeper = ActionController::Caching::Sweeper.send(:new)
- assert_nil sweeper.after(TestController.new)
- end
-
def test_non_yielding_around_filters_not_returning_false_do_not_raise
controller = NonYieldingAroundFilterController.new
controller.instance_variable_set "@filter_return_value", true
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index ccca0dac17..5490d9394b 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -46,6 +46,27 @@ module ActionDispatch
assert_equal({'foo' => 'bar'}, @hash.to_hash)
end
+ def test_to_session_value
+ @hash['foo'] = 'bar'
+ assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => []}, @hash.to_session_value)
+
+ @hash.discard('foo')
+ assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => %w[foo]}, @hash.to_session_value)
+
+ @hash.now['qux'] = 1
+ assert_equal({'flashes' => {'foo' => 'bar', 'qux' => 1}, 'discard' => %w[foo qux]}, @hash.to_session_value)
+
+ @hash.sweep
+ assert_equal(nil, @hash.to_session_value)
+ end
+
+ def test_from_session_value
+ rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoNQGZsYXNoZXN7BkkiDG1lc3NhZ2UGOwBGSSIKSGVsbG8GOwBGOglAbm93MA=='
+ session = Marshal.load(Base64.decode64(rails_3_2_cookie))
+ hash = Flash::FlashHash.from_session_value(session['flash'])
+ assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
+ end
+
def test_empty?
assert @hash.empty?
@hash['zomg'] = 'bears'
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 6414ba3994..9d4356f546 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -53,8 +53,8 @@ class FlashTest < ActionController::TestCase
render :inline => "hello"
end
- # methods for test_sweep_after_halted_filter_chain
- before_filter :halt_and_redir, :only => "filter_halting_action"
+ # methods for test_sweep_after_halted_action_chain
+ before_action :halt_and_redir, only: 'filter_halting_action'
def std_action
@flash_copy = {}.update(flash)
@@ -159,7 +159,7 @@ class FlashTest < ActionController::TestCase
assert_nil session["flash"]
end
- def test_sweep_after_halted_filter_chain
+ def test_sweep_after_halted_action_chain
get :std_action
assert_nil assigns["flash_copy"]["foo"]
get :filter_halting_action
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 2dcfda02a7..90548d4294 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -2,9 +2,9 @@ require 'abstract_unit'
class HttpBasicAuthenticationTest < ActionController::TestCase
class DummyController < ActionController::Base
- before_filter :authenticate, :only => :index
- before_filter :authenticate_with_request, :only => :display
- before_filter :authenticate_long_credentials, :only => :show
+ before_action :authenticate, only: :index
+ before_action :authenticate_with_request, only: :display
+ before_action :authenticate_long_credentials, only: :show
http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index c4a94264c3..537de7a2dd 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -4,8 +4,8 @@ require 'active_support/key_generator'
class HttpDigestAuthenticationTest < ActionController::TestCase
class DummyDigestController < ActionController::Base
- before_filter :authenticate, :only => :index
- before_filter :authenticate_with_request, :only => :display
+ before_action :authenticate, only: :index
+ before_action :authenticate_with_request, only: :display
USERS = { 'lifo' => 'world', 'pretty' => 'please',
'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))}
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
index ad4e743be8..ebf6d224aa 100644
--- a/actionpack/test/controller/http_token_authentication_test.rb
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -2,9 +2,9 @@ require 'abstract_unit'
class HttpTokenAuthenticationTest < ActionController::TestCase
class DummyController < ActionController::Base
- before_filter :authenticate, :only => :index
- before_filter :authenticate_with_request, :only => :display
- before_filter :authenticate_long_credentials, :only => :show
+ before_action :authenticate, only: :index
+ before_action :authenticate_with_request, only: :display
+ before_action :authenticate_long_credentials, only: :show
def index
render :text => "Hello Secret"
@@ -104,17 +104,40 @@ class HttpTokenAuthenticationTest < ActionController::TestCase
assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
end
- test "authentication request with valid credential" do
- @request.env['HTTP_AUTHORIZATION'] = encode_credentials('"quote" pretty', :algorithm => 'test')
- get :display
+ test "token_and_options returns correct token" do
+ token = "rcHu+HzSFw89Ypyhn/896A=="
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
+
+ test "token_and_options returns correct token with value after the equal sign" do
+ token = 'rcHu+=HzSFw89Ypyhn/896A==f34'
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
- assert_response :success
- assert assigns(:logged_in)
- assert_equal 'Definitely Maybe', @response.body
+ test "token_and_options returns correct token with slashes" do
+ token = 'rcHu+\\\\"/896A'
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
+
+ test "token_and_options returns correct token with quotes" do
+ token = '\"quote\" pretty'
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
end
private
+ def sample_request(token)
+ @sample_request ||= OpenStruct.new authorization: %{Token token="#{token}"}
+ end
+
def encode_credentials(token, options = {})
ActionController::HttpAuthentication::Token.encode_credentials(token, options)
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 20e433d1ec..3b1a07d7af 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -40,7 +40,7 @@ module ActionController
def thread_locals
tc.assert_equal 'aaron', Thread.current[:setting]
- tc.refute_equal Thread.current.object_id, Thread.current[:originating_thread]
+ tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread]
response.headers['Content-Type'] = 'text/event-stream'
%w{ hello world }.each do |word|
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 9efb6ab95f..075347be52 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -13,7 +13,7 @@ module Another
head :status => 406
end
- before_filter :redirector, :only => :never_executed
+ before_action :redirector, only: :never_executed
def never_executed
end
@@ -26,6 +26,10 @@ module Another
redirect_to "http://foo.bar/"
end
+ def filterable_redirector
+ redirect_to "http://secret.foo.bar/"
+ end
+
def data_sender
send_data "cool data", :filename => "file.txt"
end
@@ -42,6 +46,22 @@ module Another
render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>"
end
+ def with_fragment_cache_if_with_true_condition
+ render :inline => "<%= cache_if(true, 'foo') { 'bar' } %>"
+ end
+
+ def with_fragment_cache_if_with_false_condition
+ render :inline => "<%= cache_if(false, 'foo') { 'bar' } %>"
+ end
+
+ def with_fragment_cache_unless_with_false_condition
+ render :inline => "<%= cache_unless(false, 'foo') { 'bar' } %>"
+ end
+
+ def with_fragment_cache_unless_with_true_condition
+ render :inline => "<%= cache_unless(true, 'foo') { 'bar' } %>"
+ end
+
def with_exception
raise Exception
end
@@ -152,6 +172,24 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_equal "Redirected to http://foo.bar/", logs[1]
end
+ def test_filter_redirect_url_by_string
+ @request.env['action_dispatch.redirect_filter'] = ['secret']
+ get :filterable_redirector
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal "Redirected to [FILTERED]", logs[1]
+ end
+
+ def test_filter_redirect_url_by_regexp
+ @request.env['action_dispatch.redirect_filter'] = [/secret\.foo.+/]
+ get :filterable_redirector
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal "Redirected to [FILTERED]", logs[1]
+ end
+
def test_send_data
get :data_sender
wait
@@ -181,6 +219,54 @@ class ACLogSubscriberTest < ActionController::TestCase
@controller.config.perform_caching = true
end
+ def test_with_fragment_cache_if_with_true
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_if_with_true_condition
+ wait
+
+ assert_equal 4, logs.size
+ assert_match(/Read fragment views\/foo/, logs[1])
+ assert_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_if_with_false
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_if_with_false_condition
+ wait
+
+ assert_equal 2, logs.size
+ assert_no_match(/Read fragment views\/foo/, logs[1])
+ assert_no_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_unless_with_true
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_unless_with_true_condition
+ wait
+
+ assert_equal 2, logs.size
+ assert_no_match(/Read fragment views\/foo/, logs[1])
+ assert_no_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_unless_with_false
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_unless_with_false_condition
+ wait
+
+ assert_equal 4, logs.size
+ assert_match(/Read fragment views\/foo/, logs[1])
+ assert_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
def test_with_fragment_cache_and_percent_in_key
@controller.config.perform_caching = true
get :with_fragment_cache_and_percent_in_key
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index d183b0be17..ed013e2185 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -1139,7 +1139,7 @@ end
# For testing layouts which are set automatically
class PostController < AbstractPostController
- around_filter :with_iphone
+ around_action :with_iphone
def index
respond_to(:html, :iphone, :js)
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index ed244513a5..964f22eb03 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
# Tests the controller dispatching happy path
module Dispatching
class SimpleController < ActionController::Base
- before_filter :authenticate
+ before_action :authenticate
def index
render :text => "success"
diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb
index f41b14d5d6..177a1c088d 100644
--- a/actionpack/test/controller/new_base/render_context_test.rb
+++ b/actionpack/test/controller/new_base/render_context_test.rb
@@ -14,7 +14,7 @@ module RenderContext
include ActionView::Context
# 2) Call _prepare_context that will do the required initialization
- before_filter :_prepare_context
+ before_action :_prepare_context
def hello_world
@value = "Hello"
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index d0be4f66d1..6b2ae2b2a9 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -9,7 +9,8 @@ module RenderTemplate
"locals.html.erb" => "The secret is <%= secret %>",
"xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
"with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
- "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %>",
+ "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in a html template",
+ "with_implicit_raw.text.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in a text template",
"test/with_json.html.erb" => "<%= render :template => 'test/with_json', :formats => [:json] %>",
"test/with_json.json.erb" => "<%= render :template => 'test/final', :formats => [:json] %>",
"test/final.json.erb" => "{ final: json }",
@@ -113,7 +114,12 @@ module RenderTemplate
get :with_implicit_raw
- assert_body "Hello <strong>this is also raw</strong>"
+ assert_body "Hello <strong>this is also raw</strong> in a html template"
+ assert_status 200
+
+ get :with_implicit_raw, format: 'text'
+
+ assert_body "Hello <strong>this is also raw</strong> in a text template"
assert_status 200
end
diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb
index f6913a2138..43a8c05cda 100644
--- a/actionpack/test/controller/output_escaping_test.rb
+++ b/actionpack/test/controller/output_escaping_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
class OutputEscapingTest < ActiveSupport::TestCase
test "escape_html shouldn't die when passed nil" do
- assert_blank ERB::Util.h(nil)
+ assert ERB::Util.h(nil).blank?
end
test "escapeHTML should escape strings" do
diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb
index d287e79cba..6df849c4e2 100644
--- a/actionpack/test/controller/parameters/nested_parameters_test.rb
+++ b/actionpack/test/controller/parameters/nested_parameters_test.rb
@@ -36,6 +36,31 @@ class NestedParametersTest < ActiveSupport::TestCase
assert_nil permitted[:magazine]
end
+ test "permitted nested parameters with a string or a symbol as a key" do
+ params = ActionController::Parameters.new({
+ book: {
+ 'authors' => [
+ { name: 'William Shakespeare', born: '1564-04-26' },
+ { name: 'Christopher Marlowe' }
+ ]
+ }
+ })
+
+ permitted = params.permit book: [ { 'authors' => [ :name ] } ]
+
+ assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name]
+ assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name]
+
+ permitted = params.permit book: [ { authors: [ :name ] } ]
+
+ assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name]
+ assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name]
+ end
+
test "nested arrays with strings" do
params = ActionController::Parameters.new({
:book => {
diff --git a/actionpack/test/controller/parameters/raise_on_unpermitted_parameters_test.rb b/actionpack/test/controller/parameters/raise_on_unpermitted_parameters_test.rb
new file mode 100644
index 0000000000..747b8123ea
--- /dev/null
+++ b/actionpack/test/controller/parameters/raise_on_unpermitted_parameters_test.rb
@@ -0,0 +1,33 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class RaiseOnUnpermittedParametersTest < ActiveSupport::TestCase
+ def setup
+ ActionController::Parameters.raise_on_unpermitted_parameters = true
+ end
+
+ def teardown
+ ActionController::Parameters.raise_on_unpermitted_parameters = false
+ end
+
+ test "raises on unexpected params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65 },
+ fishing: "Turnips"
+ })
+
+ assert_raises(ActionController::UnpermittedParameters) do
+ params.permit(book: [:pages])
+ end
+ end
+
+ test "raises on unexpected nested params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65, title: "Green Cats and where to find then." }
+ })
+
+ assert_raises(ActionController::UnpermittedParameters) do
+ params.permit(book: [:pages])
+ end
+ end
+end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index aa33f01d02..0e5bad7482 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -37,7 +37,7 @@ end
class TestController < ActionController::Base
protect_from_forgery
- before_filter :set_variable_for_layout
+ before_action :set_variable_for_layout
class LabellingFormBuilder < ActionView::Helpers::FormBuilder
end
@@ -137,7 +137,7 @@ class TestController < ActionController::Base
def conditional_hello_with_bangs
render :action => 'hello_world'
end
- before_filter :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs
+ before_action :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs
def handle_last_modified_and_etags
fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
@@ -531,6 +531,10 @@ class TestController < ActionController::Base
head :created, :content_type => "application/json"
end
+ def head_ok_with_image_png_content_type
+ head :ok, :content_type => "image/png"
+ end
+
def head_with_location_header
head :location => "/foo"
end
@@ -646,6 +650,10 @@ class TestController < ActionController::Base
render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
+ def partial_collection_with_spacer_which_uses_render
+ render :partial => "customer", :spacer_template => "partial_with_partial", :collection => [ Customer.new("david"), Customer.new("mary") ]
+ end
+
def partial_collection_shorthand_with_locals
render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" }
end
@@ -706,7 +714,7 @@ class TestController < ActionController::Base
render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout"
end
- before_filter :only => :render_with_filters do
+ before_action only: :render_with_filters do
request.format = :xml
end
@@ -785,15 +793,13 @@ class RenderTest < ActionController::TestCase
end
def test_line_offset
- begin
- get :render_line_offset
- flunk "the action should have raised an exception"
- rescue StandardError => exc
- line = exc.backtrace.first
- assert(line =~ %r{:(\d+):})
- assert_equal "1", $1,
- "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}"
- end
+ get :render_line_offset
+ flunk "the action should have raised an exception"
+ rescue StandardError => exc
+ line = exc.backtrace.first
+ assert(line =~ %r{:(\d+):})
+ assert_equal "1", $1,
+ "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}"
end
# :ported: compatibility
@@ -1213,20 +1219,27 @@ class RenderTest < ActionController::TestCase
def test_head_created
post :head_created
- assert_blank @response.body
+ assert @response.body.blank?
assert_response :created
end
def test_head_created_with_application_json_content_type
post :head_created_with_application_json_content_type
- assert_blank @response.body
- assert_equal "application/json", @response.content_type
+ assert @response.body.blank?
+ assert_equal "application/json", @response.header["Content-Type"]
assert_response :created
end
+ def test_head_ok_with_image_png_content_type
+ post :head_ok_with_image_png_content_type
+ assert @response.body.blank?
+ assert_equal "image/png", @response.header["Content-Type"]
+ assert_response :ok
+ end
+
def test_head_with_location_header
get :head_with_location_header
- assert_blank @response.body
+ assert @response.body.blank?
assert_equal "/foo", @response.headers["Location"]
assert_response :ok
end
@@ -1239,7 +1252,7 @@ class RenderTest < ActionController::TestCase
end
get :head_with_location_object
- assert_blank @response.body
+ assert @response.body.blank?
assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
assert_response :ok
end
@@ -1247,14 +1260,14 @@ class RenderTest < ActionController::TestCase
def test_head_with_custom_header
get :head_with_custom_header
- assert_blank @response.body
+ assert @response.body.blank?
assert_equal "something", @response.headers["X-Custom-Header"]
assert_response :ok
end
def test_head_with_www_authenticate_header
get :head_with_www_authenticate_header
- assert_blank @response.body
+ assert @response.body.blank?
assert_equal "something", @response.headers["WWW-Authenticate"]
assert_response :ok
end
@@ -1429,10 +1442,11 @@ class RenderTest < ActionController::TestCase
end
def test_locals_option_to_assert_template_is_not_supported
+ get :partial_collection_with_locals
+
warning_buffer = StringIO.new
$stderr = warning_buffer
- get :partial_collection_with_locals
assert_template partial: 'customer_greeting', locals: { greeting: 'Bonjour' }
assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string
ensure
@@ -1445,6 +1459,12 @@ class RenderTest < ActionController::TestCase
assert_template :partial => '_customer'
end
+ def test_partial_collection_with_spacer_which_uses_render
+ get :partial_collection_with_spacer_which_uses_render
+ assert_equal "Hello: davidpartial html\npartial with partial\nHello: mary", @response.body
+ assert_template :partial => '_customer'
+ end
+
def test_partial_collection_shorthand_with_locals
get :partial_collection_shorthand_with_locals
assert_equal "Bonjour: davidBonjour: mary", @response.body
@@ -1581,7 +1601,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = @last_modified
get :conditional_hello
assert_equal 304, @response.status.to_i
- assert_blank @response.body
+ assert @response.body.blank?
assert_equal @last_modified, @response.headers['Last-Modified']
end
@@ -1596,7 +1616,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
get :conditional_hello
assert_equal 200, @response.status.to_i
- assert_present @response.body
+ assert @response.body.present?
assert_equal @last_modified, @response.headers['Last-Modified']
end
@@ -1610,7 +1630,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = @last_modified
get :conditional_hello_with_record
assert_equal 304, @response.status.to_i
- assert_blank @response.body
+ assert @response.body.blank?
assert_equal @last_modified, @response.headers['Last-Modified']
end
@@ -1625,7 +1645,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
get :conditional_hello_with_record
assert_equal 200, @response.status.to_i
- assert_present @response.body
+ assert @response.body.present?
assert_equal @last_modified, @response.headers['Last-Modified']
end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 1f637eb791..523a8d0572 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -320,7 +320,7 @@ class FreeCookieControllerTest < ActionController::TestCase
test 'should not emit a csrf-token meta tag' do
get :meta
- assert_blank @response.body
+ assert @response.body.blank?
end
end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 48e2d6491e..4898b0c57f 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -68,9 +68,9 @@ class RescueController < ActionController::Base
render :text => 'io error'
end
- before_filter(:only => :before_filter_raises) { raise 'umm nice' }
+ before_action(only: :before_action_raises) { raise 'umm nice' }
- def before_filter_raises
+ def before_action_raises
end
def raises
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index f0430e516f..5e821046db 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -57,13 +57,13 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase
end
class MockController
- def self.build(helpers)
+ def self.build(helpers, additional_options = {})
Class.new do
- def url_options
- options = super
+ define_method :url_options do
+ options = super()
options[:protocol] ||= "http"
options[:host] ||= "test.host"
- options
+ options.merge(additional_options)
end
include helpers
@@ -428,8 +428,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
routes.send(:pages_url)
end
- def setup_for_named_route
- MockController.build(rs.url_helpers).new
+ def setup_for_named_route(options = {})
+ MockController.build(rs.url_helpers, options).new
end
def test_named_route_without_hash
@@ -456,6 +456,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal("/", routes.send(:root_path))
end
+ def test_named_route_root_with_trailing_slash
+ rs.draw do
+ root "hello#index"
+ end
+
+ routes = setup_for_named_route(trailing_slash: true)
+ assert_equal("http://test.host/", routes.send(:root_url))
+ assert_equal("http://test.host/?foo=bar", routes.send(:root_url, foo: :bar))
+ end
+
def test_named_route_with_regexps
rs.draw do
get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 8bf3096888..8ecc1c7d73 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -114,6 +114,18 @@ class SendFileTest < ActionController::TestCase
assert_equal 'private', h['Cache-Control']
end
+ def test_send_file_headers_with_disposition_as_a_symbol
+ options = {
+ :type => Mime::PNG,
+ :disposition => :disposition,
+ :filename => 'filename'
+ }
+
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+ assert_equal 'disposition; filename="filename"', @controller.headers['Content-Disposition']
+ end
+
def test_send_file_headers_with_mime_lookup_with_symbol
options = {
:type => :png
@@ -132,7 +144,7 @@ class SendFileTest < ActionController::TestCase
}
@controller.headers = {}
- assert_raise(ArgumentError){ @controller.send(:send_file_headers!, options) }
+ assert !@controller.send(:send_file_headers!, options)
end
def test_send_file_headers_guess_type_from_extension
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 718d06ef38..888791b874 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -5,7 +5,7 @@ module ShowExceptions
use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
use ActionDispatch::DebugExceptions
- before_filter :only => :another_boom do
+ before_action only: :another_boom do
request.env["action_dispatch.show_detailed_exceptions"] = true
end
diff --git a/actionpack/test/controller/spec_style_test.rb b/actionpack/test/controller/spec_style_test.rb
deleted file mode 100644
index e118c584ca..0000000000
--- a/actionpack/test/controller/spec_style_test.rb
+++ /dev/null
@@ -1,208 +0,0 @@
-require "abstract_unit"
-
-class ApplicationController < ActionController::Base; end
-class ModelsController < ApplicationController; end
-module Admin
- class WidgetsController < ApplicationController; end
-end
-
-# ApplicationController
-describe ApplicationController do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ApplicationController, @controller
- end
- end
- end
-end
-
-describe ApplicationController, :index do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ApplicationController, @controller
- end
- end
- end
-end
-
-describe ApplicationController, "unauthenticated user" do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ApplicationController, @controller
- end
- end
- end
-end
-
-describe "ApplicationControllerTest" do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ApplicationController, @controller
- end
- end
- end
-end
-
-describe "ApplicationControllerTest", :index do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ApplicationController, @controller
- end
- end
- end
-end
-
-describe "ApplicationControllerTest", "unauthenticated user" do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ApplicationController, @controller
- end
- end
- end
-end
-
-# ModelsController
-describe ModelsController do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ModelsController, @controller
- end
- end
- end
-end
-
-describe ModelsController, :index do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ModelsController, @controller
- end
- end
- end
-end
-
-describe ModelsController, "unauthenticated user" do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ModelsController, @controller
- end
- end
- end
-end
-
-describe "ModelsControllerTest" do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ModelsController, @controller
- end
- end
- end
-end
-
-describe "ModelsControllerTest", :index do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ModelsController, @controller
- end
- end
- end
-end
-
-describe "ModelsControllerTest", "unauthenticated user" do
- describe "nested" do
- describe "even deeper" do
- it "exists" do
- assert_kind_of ModelsController, @controller
- end
- end
- end
-end
-
-# Nested Admin::WidgetsControllerTest
-module Admin
- class WidgetsControllerTest < ActionController::TestCase
- test "exists" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
-
- describe WidgetsController do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
- end
-
- describe WidgetsController, "unauthenticated users" do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
- end
-end
-
-class Admin::WidgetsControllerTest < ActionController::TestCase
- test "exists here too" do
- assert_kind_of Admin::WidgetsController, @controller
- end
-end
-
-describe Admin::WidgetsController do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
-end
-
-describe Admin::WidgetsController, "unauthenticated users" do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
-end
-
-describe "Admin::WidgetsController" do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
-end
-
-describe "Admin::WidgetsControllerTest" do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
-end
-
-describe "Admin::WidgetsController", "unauthenticated users" do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
-end
-
-describe "Admin::WidgetsControllerTest", "unauthenticated users" do
- describe "index" do
- it "respond successful" do
- assert_kind_of Admin::WidgetsController, @controller
- end
- end
-end
diff --git a/actionpack/test/controller/spec_type_test.rb b/actionpack/test/controller/spec_type_test.rb
deleted file mode 100644
index 13be8a3405..0000000000
--- a/actionpack/test/controller/spec_type_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require "abstract_unit"
-
-class ApplicationController < ActionController::Base; end
-class ModelsController < ApplicationController; end
-
-class ActionControllerSpecTypeTest < ActiveSupport::TestCase
- def assert_controller actual
- assert_equal ActionController::TestCase, actual
- end
-
- def refute_controller actual
- refute_equal ActionController::TestCase, actual
- end
-
- def test_spec_type_resolves_for_class_constants
- assert_controller MiniTest::Spec.spec_type(ApplicationController)
- assert_controller MiniTest::Spec.spec_type(ModelsController)
- end
-
- def test_spec_type_resolves_for_matching_strings
- assert_controller MiniTest::Spec.spec_type("WidgetController")
- assert_controller MiniTest::Spec.spec_type("WidgetControllerTest")
- assert_controller MiniTest::Spec.spec_type("Widget Controller Test")
- # And is not case sensitive
- assert_controller MiniTest::Spec.spec_type("widgetcontroller")
- assert_controller MiniTest::Spec.spec_type("widgetcontrollertest")
- assert_controller MiniTest::Spec.spec_type("widget controller test")
- end
-
- def test_spec_type_wont_match_non_space_characters
- refute_controller MiniTest::Spec.spec_type("Widget Controller\tTest")
- refute_controller MiniTest::Spec.spec_type("Widget Controller\rTest")
- refute_controller MiniTest::Spec.spec_type("Widget Controller\nTest")
- refute_controller MiniTest::Spec.spec_type("Widget Controller\fTest")
- refute_controller MiniTest::Spec.spec_type("Widget ControllerXTest")
- end
-end
diff --git a/actionpack/test/controller/sweeper_test.rb b/actionpack/test/controller/sweeper_test.rb
deleted file mode 100644
index 0561efc62f..0000000000
--- a/actionpack/test/controller/sweeper_test.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'abstract_unit'
-
-
-class SweeperTest < ActionController::TestCase
-
- class ::AppSweeper < ActionController::Caching::Sweeper; end
-
- def test_sweeper_should_not_ignore_unknown_method_calls
- sweeper = ActionController::Caching::Sweeper.send(:new)
- assert_raise NameError do
- sweeper.instance_eval do
- some_method_that_doesnt_exist
- end
- end
- end
-end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 8990fc34d6..e4d78d58b9 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -692,7 +692,7 @@ XML
assert_equal "bar", @request.params[:foo]
@request.recycle!
post :no_op
- assert_blank @request.params[:foo]
+ assert @request.params[:foo].blank?
end
def test_symbolized_path_params_reset_after_request
@@ -818,6 +818,18 @@ XML
assert_equal '159528', @response.body
end
+ def test_fixture_file_upload_relative_to_fixture_path
+ TestCaseTest.stubs(:fixture_path).returns(FILES_DIR)
+ uploaded_file = fixture_file_upload("mona_lisa.jpg", "image/jpg")
+ assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ end
+
+ def test_fixture_file_upload_ignores_nil_fixture_path
+ TestCaseTest.stubs(:fixture_path).returns(nil)
+ uploaded_file = fixture_file_upload("#{FILES_DIR}/mona_lisa.jpg", "image/jpg")
+ assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ end
+
def test_action_dispatch_uploaded_file_upload
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index 40f6dc6f0f..c6e7a523b9 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -4,7 +4,7 @@ class ViewLoadPathsTest < ActionController::TestCase
class TestController < ActionController::Base
def self.controller_path() "test" end
- before_filter :add_view_path, :only => :hello_world_at_request_time
+ before_action :add_view_path, only: :hello_world_at_request_time
def hello_world() end
def hello_world_at_request_time() render(:action => 'hello_world') end
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index d236b14e02..1319eba9ac 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -45,8 +45,17 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
end
end
- ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false))
- DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true))
+ def setup
+ app = ActiveSupport::OrderedOptions.new
+ app.config = ActiveSupport::OrderedOptions.new
+ app.config.assets = ActiveSupport::OrderedOptions.new
+ app.config.assets.prefix = '/sprockets'
+ Rails.stubs(:application).returns(app)
+ end
+
+ RoutesApp = Struct.new(:routes).new(SharedTestRoutes)
+ ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp)
+ DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp)
test 'skip diagnosis if not showing detailed exceptions' do
@app = ProductionApp
@@ -78,6 +87,15 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert boomer.closed, "Expected to close the response body"
end
+ test 'displays routes in a table when a RoutingError occurs' do
+ @app = DevelopmentApp
+ get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ routing_table = body[/route_table.*<.table>/m]
+ assert_match '/:controller(/:action)(.:format)', routing_table
+ assert_match ':controller#:action', routing_table
+ assert_no_match '&lt;|&gt;', routing_table, "there should not be escaped html in the output"
+ end
+
test "rescue with diagnostics message" do
@app = DevelopmentApp
@@ -135,7 +153,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
}
})
assert_response 500
- assert_match(/RuntimeError\n in FeaturedTileController/, body)
+ assert_match(/RuntimeError\n\s+in FeaturedTileController/, body)
end
test "sets the HTTP charset parameter" do
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index e16f23914b..e0cfb73acf 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -11,7 +11,7 @@ module ActionController
def test_header_merge
header = @response.header.merge('Foo' => 'Bar')
assert_kind_of(ActionController::Live::Response::Header, header)
- refute_equal header, @response.header
+ assert_not_equal header, @response.header
end
def test_initialize_with_default_headers
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 63c5ea26a6..399f15199c 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -123,6 +123,18 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
end
end
+ # This can happen in Internet Explorer when redirecting after multipart form submit.
+ test "does not raise EOFError on GET request with multipart content-type" do
+ with_routing do |set|
+ set.draw do
+ get ':action', to: 'multipart_params_parsing_test/test'
+ end
+ headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
+ get "/parse", {}, headers
+ assert_response :ok
+ end
+ end
+
private
def fixture(name)
File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 3f36d4f1a9..1517f96fdc 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -24,7 +24,7 @@ module ActionDispatch
s['foo'] = 'bar'
s1 = Session.create(store, env, {})
- refute_equal s, s1
+ assert_not_equal s, s1
assert_equal 'bar', s1['foo']
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index e2964f9071..02675c7f8c 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
class RequestTest < ActiveSupport::TestCase
def url_for(options = {})
- options.reverse_merge!(:host => 'www.example.com')
+ options = { host: 'www.example.com' }.merge!(options)
ActionDispatch::Http::URL.url_for(options)
end
@@ -25,6 +25,8 @@ class RequestTest < ActiveSupport::TestCase
assert_equal 'http://www.example.com/', url_for(:trailing_slash => true)
assert_equal 'http://dhh:supersecret@www.example.com', url_for(:user => 'dhh', :password => 'supersecret')
assert_equal 'http://www.example.com?search=books', url_for(:params => { :search => 'books' })
+ assert_equal 'http://www.example.com?params=', url_for(:params => '')
+ assert_equal 'http://www.example.com?params=1', url_for(:params => 1)
end
test "remote ip" do
@@ -32,7 +34,7 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '1.2.3.4', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6'
- assert_equal '1.2.3.4', request.remote_ip
+ assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '1.2.3.4',
'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
@@ -45,30 +47,32 @@ class RequestTest < ActiveSupport::TestCase
request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown'
assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,172.16.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,192.168.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,10.0.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 10.0.0.1, 10.0.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,127.0.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
assert_equal nil, request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 172.31.4.4, 10.0.0.1'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
assert_equal nil, request.remote_ip
+ end
+ test "remote ip spoof detection" do
request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
'HTTP_CLIENT_IP' => '2.2.2.2'
e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
@@ -77,26 +81,20 @@ class RequestTest < ActiveSupport::TestCase
assert_match(/IP spoofing attack/, e.message)
assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message)
assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message)
+ end
- # turn IP Spoofing detection off.
- # This is useful for sites that are aimed at non-IP clients. The typical
- # example is WAP. Since the cellular network is not IP based, it's a
- # leap of faith to assume that their proxies are ever going to set the
- # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly.
+ test "remote ip with spoof detection disabled" do
request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
'HTTP_CLIENT_IP' => '2.2.2.2',
:ip_spoofing_check => false
- assert_equal '2.2.2.2', request.remote_ip
-
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 8.8.8.8'
- assert_equal '9.9.9.9', request.remote_ip
+ assert_equal '1.1.1.1', request.remote_ip
end
test "remote ip v6" do
request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
- request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334'
assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
@@ -107,30 +105,26 @@ class RequestTest < ActiveSupport::TestCase
'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
- assert_equal nil, request.remote_ip
-
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
- assert_equal nil, request.remote_ip
-
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,unknown'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1, ::1, fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, ::1'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1'
assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::'
- assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
assert_equal nil, request.remote_ip
+ end
+ test "remote ip v6 spoof detection" do
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
@@ -139,26 +133,15 @@ class RequestTest < ActiveSupport::TestCase
assert_match(/IP spoofing attack/, e.message)
assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message)
assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message)
+ end
- # Turn IP Spoofing detection off.
- # This is useful for sites that are aimed at non-IP clients. The typical
- # example is WAP. Since the cellular network is not IP based, it's a
- # leap of faith to assume that their proxies are ever going to set the
- # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly.
+ test "remote ip v6 spoof detection disabled" do
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
:ip_spoofing_check => false
- assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
-
- request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
end
- test "remote ip when the remote ip middleware returns nil" do
- request = stub_request 'REMOTE_ADDR' => '127.0.0.1'
- assert_equal '127.0.0.1', request.remote_ip
- end
-
test "remote ip with user specified trusted proxies String" do
@trusted_proxies = "67.205.106.73"
@@ -168,16 +151,16 @@ class RequestTest < ActiveSupport::TestCase
request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73',
'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
- assert_equal '172.16.0.1', request.remote_ip
+ assert_equal '67.205.106.73', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6',
'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73,unknown'
assert_equal nil, request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
end
@@ -194,13 +177,13 @@ class RequestTest < ActiveSupport::TestCase
request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1',
'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
- assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+ assert_equal '::1', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334'
- assert_equal nil, request.remote_ip
+ assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip
end
test "remote ip with user specified trusted proxies Regexp" do
@@ -210,8 +193,8 @@ class RequestTest < ActiveSupport::TestCase
'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 9.9.9.9, 3.4.5.6, 67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
end
test "remote ip v6 with user specified trusted proxies Regexp" do
@@ -221,8 +204,13 @@ class RequestTest < ActiveSupport::TestCase
'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
- assert_equal nil, request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+ end
+
+ test "remote ip middleware not present still returns an IP" do
+ request = ActionDispatch::Request.new({'REMOTE_ADDR' => '127.0.0.1'})
+ assert_equal '127.0.0.1', request.remote_ip
end
test "domains" do
@@ -355,7 +343,6 @@ class RequestTest < ActiveSupport::TestCase
assert_equal "/of/some/uri", request.path_info
end
-
test "host with default port" do
request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80'
assert_equal "rubyonrails.org", request.host_with_port
@@ -577,20 +564,29 @@ class RequestTest < ActiveSupport::TestCase
test "formats with accept header" do
request = stub_request 'HTTP_ACCEPT' => 'text/html'
request.expects(:parameters).at_least_once.returns({})
- assert_equal [ Mime::HTML ], request.formats
+ assert_equal [Mime::HTML], request.formats
request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
request.expects(:parameters).at_least_once.returns({})
- assert_equal with_set(Mime::XML), request.formats
+ assert_equal [Mime::XML], request.formats
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => :txt })
- assert_equal with_set(Mime::TEXT), request.formats
+ assert_equal [Mime::TEXT], request.formats
request = stub_request
request.expects(:parameters).at_least_once.returns({ :format => :unknown })
- assert request.formats.empty?
+ assert_instance_of Mime::NullType, request.format
+ end
+
+ test "format is not nil with unknown format" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ format: :hello })
+ assert_equal request.format.nil?, true
+ assert_equal request.format.html?, false
+ assert_equal request.format.xml?, false
+ assert_equal request.format.json?, false
end
test "formats with xhr request" do
@@ -649,6 +645,13 @@ class RequestTest < ActiveSupport::TestCase
assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
end
+ test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do
+ request = stub_request('rack.input' => StringIO.new("foo"),
+ 'CONTENT_LENGTH' => 3)
+ assert_equal "foo", request.raw_post
+ assert_equal "foo", request.env['rack.input'].read
+ end
+
test "process parameter filter" do
test_hashes = [
[{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
@@ -811,8 +814,4 @@ protected
ActionDispatch::Http::URL.tld_length = tld_length
ActionDispatch::Request.new(env)
end
-
- def with_set(*args)
- args
- end
end
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
index a3034ce001..c7dcb5a683 100644
--- a/actionpack/test/dispatch/routing/inspector_test.rb
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -1,5 +1,4 @@
-require 'minitest/autorun'
-require 'action_controller'
+require 'abstract_unit'
require 'rails/engine'
require 'action_dispatch/routing/inspector'
@@ -8,7 +7,6 @@ module ActionDispatch
class RoutesInspectorTest < ActiveSupport::TestCase
def setup
@set = ActionDispatch::Routing::RouteSet.new
- @inspector = ActionDispatch::Routing::RoutesInspector.new
app = ActiveSupport::OrderedOptions.new
app.config = ActiveSupport::OrderedOptions.new
app.config.assets = ActiveSupport::OrderedOptions.new
@@ -17,9 +15,10 @@ module ActionDispatch
Rails.stubs(:env).returns("development")
end
- def draw(&block)
+ def draw(options = {}, &block)
@set.draw(&block)
- @inspector.format(@set.routes)
+ inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes)
+ inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
end
def test_displaying_routes_for_engines
@@ -40,7 +39,8 @@ module ActionDispatch
expected = [
"custom_assets GET /custom/assets(.:format) custom_assets#show",
" blog /blog Blog::Engine",
- "\nRoutes for Blog::Engine:",
+ "",
+ "Routes for Blog::Engine:",
"cart GET /cart(.:format) cart#show"
]
assert_equal expected, output
@@ -165,6 +165,22 @@ module ActionDispatch
assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1]
assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2]
end
+
+ def test_routes_can_be_filtered
+ output = draw(filter: 'posts') do
+ resources :articles
+ resources :posts
+ end
+
+ assert_equal [" posts GET /posts(.:format) posts#index",
+ " POST /posts(.:format) posts#create",
+ " new_post GET /posts/new(.:format) posts#new",
+ "edit_post GET /posts/:id/edit(.:format) posts#edit",
+ " post GET /posts/:id(.:format) posts#show",
+ " PATCH /posts/:id(.:format) posts#update",
+ " PUT /posts/:id(.:format) posts#update",
+ " DELETE /posts/:id(.:format) posts#destroy"], output
+ end
end
end
end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
new file mode 100644
index 0000000000..d57b1a5637
--- /dev/null
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -0,0 +1,86 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class RouteSetTest < ActiveSupport::TestCase
+ class SimpleApp
+ def initialize(response)
+ @response = response
+ end
+
+ def call(env)
+ [ 200, { 'Content-Type' => 'text/plain' }, [response] ]
+ end
+ end
+
+ setup do
+ @set = RouteSet.new
+ end
+
+ test "url helpers are added when route is added" do
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_raises NoMethodError do
+ assert_equal '/bar', url_helpers.bar_path
+ end
+
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ get 'bar', to: SimpleApp.new('bar#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_equal '/bar', url_helpers.bar_path
+ end
+
+ test "url helpers are updated when route is updated" do
+ draw do
+ get 'bar', to: SimpleApp.new('bar#index'), as: :bar
+ end
+
+ assert_equal '/bar', url_helpers.bar_path
+
+ draw do
+ get 'baz', to: SimpleApp.new('baz#index'), as: :bar
+ end
+
+ assert_equal '/baz', url_helpers.bar_path
+ end
+
+ test "url helpers are removed when route is removed" do
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ get 'bar', to: SimpleApp.new('bar#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_equal '/bar', url_helpers.bar_path
+
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_raises NoMethodError do
+ assert_equal '/bar', url_helpers.bar_path
+ end
+ end
+
+ private
+ def clear!
+ @set.clear!
+ end
+
+ def draw(&block)
+ @set.draw(&block)
+ end
+
+ def url_helpers
+ @set.url_helpers
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 34606512dc..cb5299e8d3 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -20,584 +20,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- stub_controllers do |routes|
- Routes = routes
- Routes.draw do
- default_url_options :host => "rubyonrails.org"
- resources_path_names :correlation_indexes => "info_about_correlation_indexes"
-
+ def test_logout
+ draw do
controller :sessions do
- get 'login' => :new
- post 'login' => :create
delete 'logout' => :destroy
end
-
- resource :session do
- get :create
- post :reset
-
- resource :info
-
- member do
- get :crush
- end
- end
-
- scope "bookmark", :controller => "bookmarks", :as => :bookmark do
- get :new, :path => "build"
- post :create, :path => "create", :as => ""
- put :update
- get :remove, :action => :destroy, :as => :remove
- end
-
- scope "pagemark", :controller => "pagemarks", :as => :pagemark do
- get "new", :path => "build"
- post "create", :as => ""
- put "update"
- get "remove", :action => :destroy, :as => :remove
- end
-
- get 'account/logout' => redirect("/logout"), :as => :logout_redirect
- get 'account/login', :to => redirect("/login")
- get 'secure', :to => redirect("/secure/login")
-
- get 'mobile', :to => redirect(:subdomain => 'mobile')
- get 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '')
- get 'new_documentation', :to => redirect(:path => '/documentation/new')
- get 'super_new_documentation', :to => redirect(:host => 'super-docs.com')
-
- get 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
- get 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}')
-
- get 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector)
-
- constraints(lambda { |req| true }) do
- get 'account/overview'
- end
-
- get '/account/nested/overview'
- get 'sign_in' => "sessions#new"
-
- get 'account/modulo/:name', :to => redirect("/%{name}s")
- get 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" }
- get 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
-
- get 'account/google' => redirect('http://www.google.com/', :status => 302)
-
- match 'openid/login', :via => [:get, :post], :to => "openid#login"
-
- controller(:global) do
- get 'global/hide_notice'
- get 'global/export', :to => :export, :as => :export_request
- get '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
- get 'global/:action'
- end
-
- get "/local/:action", :controller => "local"
-
- get "/projects/status(.:format)"
- get "/404", :to => lambda { |env| [404, {"Content-Type" => "text/plain"}, ["NOT FOUND"]] }
-
- constraints(:ip => /192\.168\.1\.\d\d\d/) do
- get 'admin' => "queenbee#index"
- end
-
- constraints ::TestRoutingMapper::IpRestrictor do
- get 'admin/accounts' => "queenbee#accounts"
- end
-
- get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor
-
- scope 'pt', :as => 'pt' do
- resources :projects, :path_names => { :edit => 'editar', :new => 'novo' }, :path => 'projetos' do
- post :preview, :on => :new
- put :close, :on => :member, :path => 'fechar'
- get :open, :on => :new, :path => 'abrir'
- end
- resource :admin, :path_names => { :new => 'novo', :activate => 'ativar' }, :path => 'administrador' do
- post :preview, :on => :new
- put :activate, :on => :member
- end
- resources :products, :path_names => { :new => 'novo' } do
- new do
- post :preview
- end
- end
- end
-
- resources :projects, :controller => :project do
- resources :involvements, :attachments
- get :correlation_indexes, :on => :collection
-
- resources :participants do
- put :update_all, :on => :collection
- end
-
- resources :companies do
- resources :people
- resource :avatar, :controller => :avatar
- end
-
- resources :images, :as => :funny_images do
- post :revise, :on => :member
- end
-
- resource :manager, :as => :super_manager do
- post :fire
- end
-
- resources :people do
- nested do
- scope "/:access_token" do
- resource :avatar
- end
- end
-
- member do
- get 'some_path_with_name'
- put :accessible_projects
- post :resend, :generate_new_password
- end
- end
-
- resources :posts do
- get :archive, :toggle_view, :on => :collection
- post :preview, :on => :member
-
- resource :subscription
-
- resources :comments do
- post :preview, :on => :collection
- end
- end
-
- post 'new', :action => 'new', :on => :collection, :as => :new
- end
-
- resources :replies do
- collection do
- get 'page/:page' => 'replies#index', :page => %r{\d+}
- get ':page' => 'replies#index', :page => %r{\d+}
- end
-
- new do
- post :preview
- end
-
- member do
- put :answer, :to => :mark_as_answer
- delete :answer, :to => :unmark_as_answer
- end
- end
-
- resources :posts, :only => [:index, :show] do
- namespace :admin do
- root :to => "index#index"
- end
- resources :comments, :except => :destroy do
- get "views" => "comments#views", :as => :views
- end
- end
-
- resource :past, :only => :destroy
- resource :present, :only => :update
- resource :future, :only => :create
- resources :relationships, :only => [:create, :destroy]
- resources :friendships, :only => [:update]
-
- shallow do
- namespace :api do
- resources :teams do
- resources :players
- resource :captain
- end
- end
- end
-
- scope '/hello' do
- shallow do
- resources :notes do
- resources :trackbacks
- end
- end
- end
-
- resources :threads, :shallow => true do
- resource :owner
- resources :messages do
- resources :comments do
- member do
- post :preview
- end
- end
- end
- end
-
- resources :sheep do
- get "_it", :on => :member
- end
-
- resources :clients do
- namespace :google do
- resource :account do
- namespace :secret do
- resource :info
- end
- end
- end
- end
-
- resources :customers do
- get :recent, :on => :collection
- get "profile", :on => :member
- get "secret/profile" => "customers#secret", :on => :member
- post "preview" => "customers#preview", :as => :another_preview, :on => :new
- resource :avatar do
- get "thumbnail" => "avatars#thumbnail", :as => :thumbnail, :on => :member
- end
- resources :invoices do
- get "outstanding" => "invoices#outstanding", :on => :collection
- get "overdue", :to => :overdue, :on => :collection
- get "print" => "invoices#print", :as => :print, :on => :member
- post "preview" => "invoices#preview", :as => :preview, :on => :new
- get "aged/:months", :on => :collection, :action => :aged, :as => :aged
- end
- resources :notes, :shallow => true do
- get "preview" => "notes#preview", :as => :preview, :on => :new
- get "print" => "notes#print", :as => :print, :on => :member
- end
- get "inactive", :on => :collection
- post "deactivate", :on => :member
- get "old", :on => :collection, :as => :stale
- get "export"
- end
-
- namespace :api do
- resources :customers do
- get "recent" => "customers#recent", :as => :recent, :on => :collection
- get "profile" => "customers#profile", :as => :profile, :on => :member
- post "preview" => "customers#preview", :as => :preview, :on => :new
- end
- scope(':version', :version => /.+/) do
- resources :users, :id => /.+?/, :format => /json|xml/
- end
-
- get "products/list"
- end
-
- get 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
-
- get 'people/:id/update', :to => 'people#update', :as => :update_person
- get '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
-
- # misc
- get 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
-
- # default params
- get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
- get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
- defaults :id => 'home' do
- get 'scoped_pages/(:id)', :to => 'pages#show'
- end
-
- namespace :account do
- get 'shorthand'
- get 'description', :to => :description, :as => "description"
- get ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
- resource :subscription, :credit, :credit_card
-
- root :to => "account#index"
-
- namespace :admin do
- resource :subscription
- end
- end
-
- namespace :forum do
- resources :products, :path => '' do
- resources :questions
- end
- end
-
- namespace :users, :path => 'usuarios' do
- root :to => 'home#index'
- end
-
- controller :articles do
- scope '/articles', :as => 'article' do
- scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
- get '/:id', :to => :with_id, :as => ""
- end
- end
- end
-
- scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do
- resources :rooms
- end
-
- get '/info' => 'projects#info', :as => 'info'
-
- namespace :admin do
- scope '(:locale)', :locale => /en|pl/ do
- resources :descriptions
- end
- end
-
- scope '(:locale)', :locale => /en|pl/ do
- get "registrations/new"
- resources :descriptions
- root :to => 'projects#index'
- end
-
- scope :only => [:index, :show] do
- resources :products, :constraints => { :id => /\d{4}/ } do
- root :to => "products#root"
- get :favorite, :on => :collection
- resources :images
- end
- resource :account
- end
-
- resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
-
- resource :token, :module => :api
- scope :module => :api do
- resources :errors, :shallow => true do
- resources :notices
- end
- end
-
- scope :path => 'api' do
- resource :me
- get '/' => 'mes#index'
- end
-
- get "(/:username)/followers" => "followers#index"
- get "/groups(/user/:username)" => "groups#index"
- get "(/user/:username)/photos" => "photos#index"
-
- scope '(groups)' do
- scope '(discussions)' do
- resources :messages
- end
- end
-
- get "whatever/:controller(/:action(/:id))", :id => /\d+/
-
- resource :profile do
- get :settings
-
- new do
- post :preview
- end
- end
-
- resources :content
-
- namespace :transport do
- resources :taxis
- end
-
- namespace :medical do
- resource :taxis
- end
-
- scope :constraints => { :id => /\d+/ } do
- get '/tickets', :to => 'tickets#index', :as => :tickets
- end
-
- scope :constraints => { :id => /\d{4}/ } do
- resources :movies do
- resources :reviews
- resource :trailer
- end
- end
-
- namespace :private do
- root :to => redirect('/private/index')
- get "index", :to => 'private#index'
- end
-
- scope :only => [:index, :show] do
- namespace :only do
- resources :clubs do
- resources :players
- resource :chairman
- end
- end
- end
-
- scope :except => [:new, :create, :edit, :update, :destroy] do
- namespace :except do
- resources :clubs do
- resources :players
- resource :chairman
- end
- end
- end
-
- namespace :wiki do
- resources :articles, :id => /[^\/]+/ do
- resources :comments, :only => [:create, :new]
- end
- end
-
- resources :wiki_pages, :path => :pages
- resource :wiki_account, :path => :my_account
-
- scope :only => :show do
- namespace :only do
- resources :sectors, :only => :index do
- resources :companies do
- scope :only => :index do
- resources :divisions
- end
- scope :except => [:show, :update, :destroy] do
- resources :departments
- end
- end
- resource :leader
- resources :managers, :except => [:show, :update, :destroy]
- end
- end
- end
-
- scope :except => :index do
- namespace :except do
- resources :sectors, :except => [:show, :update, :destroy] do
- resources :companies do
- scope :except => [:show, :update, :destroy] do
- resources :divisions
- end
- scope :only => :index do
- resources :departments
- end
- end
- resource :leader
- resources :managers, :only => :index
- end
- end
- end
-
- resources :sections, :id => /.+/ do
- get :preview, :on => :member
- end
-
- resources :profiles, :param => :username, :username => /[a-z]+/ do
- get :details, :on => :member
- resources :messages
- end
-
- resources :orders do
- constraints :download => /[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}/ do
- resources :downloads, :param => :download, :shallow => true
- end
- end
-
- scope :as => "routes" do
- get "/c/:id", :as => :collision, :to => "collision#show"
- get "/collision", :to => "collision#show"
- get "/no_collision", :to => "collision#show", :as => nil
-
- get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
- get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
- end
-
- get '/purchases/:token/:filename',
- :to => 'purchases#fetch',
- :token => /[[:alnum:]]{10}/,
- :filename => /(.+)/,
- :as => :purchase
-
- resources :lists, :id => /([A-Za-z0-9]{25})|default/ do
- resources :todos, :id => /\d+/
- end
-
- scope '/countries/:country', :constraints => lambda { |params, req| %w(all France).include?(params[:country]) } do
- get '/', :to => 'countries#index'
- get '/cities', :to => 'countries#cities'
- end
-
- get '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
-
- get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
-
- scope '/italians' do
- get '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
- get '/sculptors', :to => 'italians#sculptors'
- get '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
- end
- end
- end
-
- class TestAltApp < ActionDispatch::IntegrationTest
- class AltRequest
- def initialize(env)
- @env = env
- end
-
- def path_info
- "/"
- end
-
- def request_method
- "GET"
- end
-
- def ip
- "127.0.0.1"
- end
-
- def x_header
- @env["HTTP_X_HEADER"] || ""
- end
- end
-
- class XHeader
- def call(env)
- [200, {"Content-Type" => "text/html"}, ["XHeader"]]
- end
- end
-
- class AltApp
- def call(env)
- [200, {"Content-Type" => "text/html"}, ["Alternative App"]]
- end
- end
-
- AltRoutes = ActionDispatch::Routing::RouteSet.new(AltRequest)
- AltRoutes.draw do
- get "/" => TestRoutingMapper::TestAltApp::XHeader.new, :constraints => {:x_header => /HEADER/}
- get "/" => TestRoutingMapper::TestAltApp::AltApp.new
end
- def app
- AltRoutes
- end
-
- def test_alt_request_without_header
- get "/"
- assert_equal "Alternative App", @response.body
- end
-
- def test_alt_request_with_matched_header
- get "/", {}, "HTTP_X_HEADER" => "HEADER"
- assert_equal "XHeader", @response.body
- end
-
- def test_alt_request_with_unmatched_header
- get "/", {}, "HTTP_X_HEADER" => "NON_MATCH"
- assert_equal "Alternative App", @response.body
- end
- end
-
- def app
- Routes
- end
-
- include Routes.url_helpers
-
- def test_logout
delete '/logout'
assert_equal 'sessions#destroy', @response.body
@@ -606,6 +35,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_login
+ draw do
+ default_url_options :host => "rubyonrails.org"
+
+ controller :sessions do
+ get 'login' => :new
+ post 'login' => :create
+ end
+ end
+
get '/login'
assert_equal 'sessions#new', @response.body
assert_equal '/login', login_path
@@ -616,39 +54,59 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true)
assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
- assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create')
- assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url
+ assert_equal 'http://rubyonrails.org/login', url_for(:controller => 'sessions', :action => 'create')
+ assert_equal 'http://rubyonrails.org/login', login_url
end
def test_login_redirect
+ draw do
+ get 'account/login', :to => redirect("/login")
+ end
+
get '/account/login'
verify_redirect 'http://www.example.com/login'
end
def test_logout_redirect_without_to
+ draw do
+ get 'account/logout' => redirect("/logout"), :as => :logout_redirect
+ end
+
assert_equal '/account/logout', logout_redirect_path
get '/account/logout'
verify_redirect 'http://www.example.com/logout'
end
def test_namespace_redirect
+ draw do
+ namespace :private do
+ root :to => redirect('/private/index')
+ get "index", :to => 'private#index'
+ end
+ end
+
get '/private'
verify_redirect 'http://www.example.com/private/index'
end
def test_namespace_with_controller_segment
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw do
- namespace :admin do
- get '/:controller(/:action(/:id(.:format)))'
- end
+ draw do
+ namespace :admin do
+ get '/:controller(/:action(/:id(.:format)))'
end
end
end
end
def test_session_singleton_resource
+ draw do
+ resource :session do
+ get :create
+ post :reset
+ end
+ end
+
get '/session'
assert_equal 'sessions#create', @response.body
assert_equal '/session', session_path
@@ -676,68 +134,126 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_session_info_nested_singleton_resource
+ draw do
+ resource :session do
+ resource :info
+ end
+ end
+
get '/session/info'
assert_equal 'infos#show', @response.body
assert_equal '/session/info', session_info_path
end
def test_member_on_resource
+ draw do
+ resource :session do
+ member do
+ get :crush
+ end
+ end
+ end
+
get '/session/crush'
assert_equal 'sessions#crush', @response.body
assert_equal '/session/crush', crush_session_path
end
def test_redirect_modulo
+ draw do
+ get 'account/modulo/:name', :to => redirect("/%{name}s")
+ end
+
get '/account/modulo/name'
verify_redirect 'http://www.example.com/names'
end
def test_redirect_proc
+ draw do
+ get 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" }
+ end
+
get '/account/proc/person'
verify_redirect 'http://www.example.com/people'
end
def test_redirect_proc_with_request
+ draw do
+ get 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
+ end
+
get '/account/proc_req'
verify_redirect 'http://www.example.com/GET'
end
def test_redirect_hash_with_subdomain
+ draw do
+ get 'mobile', :to => redirect(:subdomain => 'mobile')
+ end
+
get '/mobile'
verify_redirect 'http://mobile.example.com/mobile'
end
def test_redirect_hash_with_domain_and_path
+ draw do
+ get 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '')
+ end
+
get '/documentation'
verify_redirect 'http://www.example-documentation.com'
end
def test_redirect_hash_with_path
+ draw do
+ get 'new_documentation', :to => redirect(:path => '/documentation/new')
+ end
+
get '/new_documentation'
verify_redirect 'http://www.example.com/documentation/new'
end
def test_redirect_hash_with_host
+ draw do
+ get 'super_new_documentation', :to => redirect(:host => 'super-docs.com')
+ end
+
get '/super_new_documentation?section=top'
verify_redirect 'http://super-docs.com/super_new_documentation?section=top'
end
def test_redirect_hash_path_substitution
+ draw do
+ get 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
+ end
+
get '/stores/iernest'
verify_redirect 'http://stores.example.com/iernest'
end
def test_redirect_hash_path_substitution_with_catch_all
+ draw do
+ get 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}')
+ end
+
get '/stores/iernest/products'
verify_redirect 'http://stores.example.com/iernest/products'
end
def test_redirect_class
+ draw do
+ get 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector)
+ end
+
get '/youtube_favorites/oHg5SJYRHA0/rick-rolld'
verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
end
def test_openid
+ draw do
+ match 'openid/login', :via => [:get, :post], :to => "openid#login"
+ end
+
get '/openid/login'
assert_equal 'openid#login', @response.body
@@ -746,6 +262,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_bookmarks
+ draw do
+ scope "bookmark", :controller => "bookmarks", :as => :bookmark do
+ get :new, :path => "build"
+ post :create, :path => "create", :as => ""
+ put :update
+ get :remove, :action => :destroy, :as => :remove
+ end
+ end
+
get '/bookmark/build'
assert_equal 'bookmarks#new', @response.body
assert_equal '/bookmark/build', bookmark_new_path
@@ -764,6 +289,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_pagemarks
+ draw do
+ scope "pagemark", :controller => "pagemarks", :as => :pagemark do
+ get "new", :path => "build"
+ post "create", :as => ""
+ put "update"
+ get "remove", :action => :destroy, :as => :remove
+ end
+ end
+
get '/pagemark/build'
assert_equal 'pagemarks#new', @response.body
assert_equal '/pagemark/build', pagemark_new_path
@@ -782,6 +316,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_admin
+ draw do
+ constraints(:ip => /192\.168\.1\.\d\d\d/) do
+ get 'admin' => "queenbee#index"
+ end
+
+ constraints ::TestRoutingMapper::IpRestrictor do
+ get 'admin/accounts' => "queenbee#accounts"
+ end
+
+ get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor
+ end
+
get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
assert_equal 'queenbee#index', @response.body
@@ -802,6 +348,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_global
+ draw do
+ controller(:global) do
+ get 'global/hide_notice'
+ get 'global/export', :to => :export, :as => :export_request
+ get '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ get 'global/:action'
+ end
+ end
+
get '/global/dashboard'
assert_equal 'global#dashboard', @response.body
@@ -820,12 +375,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_local
+ draw do
+ get "/local/:action", :controller => "local"
+ end
+
get '/local/dashboard'
assert_equal 'local#dashboard', @response.body
end
# tests the use of dup in url_for
def test_url_for_with_no_side_effects
+ draw do
+ get "/projects/status(.:format)"
+ end
+
# without dup, additional (and possibly unwanted) values will be present in the options (eg. :host)
original_options = {:controller => 'projects', :action => 'status'}
options = original_options.dup
@@ -837,6 +400,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_url_for_does_not_modify_controller
+ draw do
+ get "/projects/status(.:format)"
+ end
+
controller = '/projects'
options = {:controller => controller, :action => 'status', :only_path => true}
url = url_for(options)
@@ -847,6 +414,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
# tests the arguments modification free version of define_hash_access
def test_named_route_with_no_side_effects
+ draw do
+ resources :customers do
+ get "profile", :on => :member
+ end
+ end
+
original_options = { :host => 'test.host' }
options = original_options.dup
@@ -857,11 +430,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_status
+ draw do
+ get "/projects/status(.:format)"
+ end
+
assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true)
end
def test_projects
+ draw do
+ resources :projects, :controller => :project
+ end
+
get '/projects'
assert_equal 'project#index', @response.body
assert_equal '/projects', projects_path
@@ -895,12 +476,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_with_post_action_and_new_path_on_collection
+ draw do
+ resources :projects, :controller => :project do
+ post 'new', :action => 'new', :on => :collection, :as => :new
+ end
+ end
+
post '/projects/new'
assert_equal "project#new", @response.body
assert_equal "/projects/new", new_projects_path
end
def test_projects_involvements
+ draw do
+ resources :projects, :controller => :project do
+ resources :involvements, :attachments
+ end
+ end
+
get '/projects/1/involvements'
assert_equal 'involvements#index', @response.body
assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1')
@@ -925,12 +518,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_attachments
+ draw do
+ resources :projects, :controller => :project do
+ resources :involvements, :attachments
+ end
+ end
+
get '/projects/1/attachments'
assert_equal 'attachments#index', @response.body
assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1')
end
def test_projects_participants
+ draw do
+ resources :projects, :controller => :project do
+ resources :participants do
+ put :update_all, :on => :collection
+ end
+ end
+ end
+
get '/projects/1/participants'
assert_equal 'participants#index', @response.body
assert_equal '/projects/1/participants', project_participants_path(:project_id => '1')
@@ -941,6 +548,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_companies
+ draw do
+ resources :projects, :controller => :project do
+ resources :companies do
+ resources :people
+ resource :avatar, :controller => :avatar
+ end
+ end
+ end
+
get '/projects/1/companies'
assert_equal 'companies#index', @response.body
assert_equal '/projects/1/companies', project_companies_path(:project_id => '1')
@@ -955,6 +571,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_project_manager
+ draw do
+ resources :projects do
+ resource :manager, :as => :super_manager do
+ post :fire
+ end
+ end
+ end
+
get '/projects/1/manager'
assert_equal 'managers#show', @response.body
assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1')
@@ -969,6 +593,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_project_images
+ draw do
+ resources :projects do
+ resources :images, :as => :funny_images do
+ post :revise, :on => :member
+ end
+ end
+ end
+
get '/projects/1/images'
assert_equal 'images#index', @response.body
assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1')
@@ -983,6 +615,23 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_people
+ draw do
+ resources :projects do
+ resources :people do
+ nested do
+ scope "/:access_token" do
+ resource :avatar
+ end
+ end
+
+ member do
+ put :accessible_projects
+ post :resend, :generate_new_password
+ end
+ end
+ end
+ end
+
get '/projects/1/people'
assert_equal 'people#index', @response.body
assert_equal '/projects/1/people', project_people_path(:project_id => '1')
@@ -1009,12 +658,35 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_with_resources_path_names
+ draw do
+ resources_path_names :correlation_indexes => "info_about_correlation_indexes"
+
+ resources :projects do
+ get :correlation_indexes, :on => :collection
+ end
+ end
+
get '/projects/info_about_correlation_indexes'
- assert_equal 'project#correlation_indexes', @response.body
+ assert_equal 'projects#correlation_indexes', @response.body
assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path
end
def test_projects_posts
+ draw do
+ resources :projects do
+ resources :posts do
+ get :archive, :toggle_view, :on => :collection
+ post :preview, :on => :member
+
+ resource :subscription
+
+ resources :comments do
+ post :preview, :on => :collection
+ end
+ end
+ end
+ end
+
get '/projects/1/posts'
assert_equal 'posts#index', @response.body
assert_equal '/projects/1/posts', project_posts_path(:project_id => '1')
@@ -1045,6 +717,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_replies
+ draw do
+ resources :replies do
+ member do
+ put :answer, :to => :mark_as_answer
+ delete :answer, :to => :unmark_as_answer
+ end
+ end
+ end
+
put '/replies/1/answer'
assert_equal 'replies#mark_as_answer', @response.body
@@ -1053,6 +734,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resource_routes_with_only_and_except
+ draw do
+ resources :posts, :only => [:index, :show] do
+ resources :comments, :except => :destroy
+ end
+ end
+
get '/posts'
assert_equal 'posts#index', @response.body
assert_equal '/posts', posts_path
@@ -1076,6 +763,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resource_routes_only_create_update_destroy
+ draw do
+ resource :past, :only => :destroy
+ resource :present, :only => :update
+ resource :future, :only => :create
+ end
+
delete '/past'
assert_equal 'pasts#destroy', @response.body
assert_equal '/past', past_path
@@ -1094,6 +787,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resources_routes_only_create_update_destroy
+ draw do
+ resources :relationships, :only => [:create, :destroy]
+ resources :friendships, :only => [:update]
+ end
+
post '/relationships'
assert_equal 'relationships#create', @response.body
assert_equal '/relationships', relationships_path
@@ -1112,12 +810,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resource_with_slugs_in_ids
+ draw do
+ resources :posts
+ end
+
get '/posts/rails-rocks'
assert_equal 'posts#show', @response.body
assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks')
end
def test_resources_for_uncountable_names
+ draw do
+ resources :sheep do
+ get "_it", :on => :member
+ end
+ end
+
assert_equal '/sheep', sheep_index_path
assert_equal '/sheep/1', sheep_path(1)
assert_equal '/sheep/new', new_sheep_path
@@ -1127,25 +835,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_resource_does_not_modify_passed_options
options = {:id => /.+?/, :format => /json|xml/}
- self.class.stub_controllers do |routes|
- routes.draw do
- resource :user, options
- end
- end
+ draw { resource :user, options }
assert_equal({:id => /.+?/, :format => /json|xml/}, options)
end
def test_resources_does_not_modify_passed_options
options = {:id => /.+?/, :format => /json|xml/}
- self.class.stub_controllers do |routes|
- routes.draw do
- resources :users, options
- end
- end
+ draw { resources :users, options }
assert_equal({:id => /.+?/, :format => /json|xml/}, options)
end
def test_path_names
+ draw do
+ scope 'pt', :as => 'pt' do
+ resources :projects, :path_names => { :edit => 'editar', :new => 'novo' }, :path => 'projetos'
+ resource :admin, :path_names => { :new => 'novo', :activate => 'ativar' }, :path => 'administrador' do
+ put :activate, :on => :member
+ end
+ end
+ end
+
get '/pt/projetos'
assert_equal 'projects#index', @response.body
assert_equal '/pt/projetos', pt_projects_path
@@ -1168,6 +877,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_path_option_override
+ draw do
+ scope 'pt', :as => 'pt' do
+ resources :projects, :path_names => { :new => 'novo' }, :path => 'projetos' do
+ put :close, :on => :member, :path => 'fechar'
+ get :open, :on => :new, :path => 'abrir'
+ end
+ end
+ end
+
get '/pt/projetos/novo/abrir'
assert_equal 'projects#open', @response.body
assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path
@@ -1178,11 +896,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_sprockets
+ draw do
+ get 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
+ end
+
get '/sprockets.js'
assert_equal 'javascripts', @response.body
end
def test_update_person_route
+ draw do
+ get 'people/:id/update', :to => 'people#update', :as => :update_person
+ end
+
get '/people/1/update'
assert_equal 'people#update', @response.body
@@ -1190,6 +916,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_update_project_person
+ draw do
+ get '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
+ end
+
get '/projects/1/people/2/update'
assert_equal 'people#update', @response.body
@@ -1197,6 +927,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_forum_products
+ draw do
+ namespace :forum do
+ resources :products, :path => '' do
+ resources :questions
+ end
+ end
+ end
+
get '/forum'
assert_equal 'forum/products#index', @response.body
assert_equal '/forum', forum_products_path
@@ -1215,6 +953,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_articles_perma
+ draw do
+ get 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
+ end
+
get '/articles/2009/08/18/rails-3'
assert_equal 'articles#show', @response.body
@@ -1222,6 +964,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_account_namespace
+ draw do
+ namespace :account do
+ resource :subscription, :credit, :credit_card
+ end
+ end
+
get '/account/subscription'
assert_equal 'account/subscriptions#show', @response.body
assert_equal '/account/subscription', account_subscription_path
@@ -1236,12 +984,32 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_nested_namespace
+ draw do
+ namespace :account do
+ namespace :admin do
+ resource :subscription
+ end
+ end
+ end
+
get '/account/admin/subscription'
assert_equal 'account/admin/subscriptions#show', @response.body
assert_equal '/account/admin/subscription', account_admin_subscription_path
end
def test_namespace_nested_in_resources
+ draw do
+ resources :clients do
+ namespace :google do
+ resource :account do
+ namespace :secret do
+ resource :info
+ end
+ end
+ end
+ end
+ end
+
get '/clients/1/google/account'
assert_equal '/clients/1/google/account', client_google_account_path(1)
assert_equal 'google/accounts#show', @response.body
@@ -1252,12 +1020,28 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_namespace_with_options
+ draw do
+ namespace :users, :path => 'usuarios' do
+ root :to => 'home#index'
+ end
+ end
+
get '/usuarios'
assert_equal '/usuarios', users_root_path
assert_equal 'users/home#index', @response.body
end
def test_articles_with_id
+ draw do
+ controller :articles do
+ scope '/articles', :as => 'article' do
+ scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
+ get '/:id', :to => :with_id, :as => ""
+ end
+ end
+ end
+ end
+
get '/articles/rails/1'
assert_equal 'articles#with_id', @response.body
@@ -1268,6 +1052,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_access_token_rooms
+ draw do
+ scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do
+ resources :rooms
+ end
+ end
+
get '/12345/rooms'
assert_equal 'rooms#index', @response.body
@@ -1279,40 +1069,91 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_root
+ draw do
+ root :to => 'projects#index'
+ end
+
assert_equal '/', root_path
get '/'
assert_equal 'projects#index', @response.body
end
+ def test_scoped_root
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ root :to => 'projects#index'
+ end
+ end
+
+ assert_equal '/en', root_path(:locale => 'en')
+ get '/en'
+ assert_equal 'projects#index', @response.body
+ end
+
def test_index
+ draw do
+ get '/info' => 'projects#info', :as => 'info'
+ end
+
assert_equal '/info', info_path
get '/info'
assert_equal 'projects#info', @response.body
end
def test_match_shorthand_with_no_scope
+ draw do
+ get 'account/overview'
+ end
+
assert_equal '/account/overview', account_overview_path
get '/account/overview'
assert_equal 'account#overview', @response.body
end
def test_match_shorthand_inside_namespace
+ draw do
+ namespace :account do
+ get 'shorthand'
+ end
+ end
+
assert_equal '/account/shorthand', account_shorthand_path
get '/account/shorthand'
assert_equal 'account#shorthand', @response.body
end
def test_match_shorthand_inside_namespace_with_controller
+ draw do
+ namespace :api do
+ get "products/list"
+ end
+ end
+
assert_equal '/api/products/list', api_products_list_path
get '/api/products/list'
assert_equal 'api/products#list', @response.body
end
def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
+ draw do
+ resources :replies do
+ collection do
+ get 'page/:page' => 'replies#index', :page => %r{\d+}
+ get ':page' => 'replies#index', :page => %r{\d+}
+ end
+ end
+ end
+
assert_equal '/replies', replies_path
end
def test_scoped_controller_with_namespace_and_action
+ draw do
+ namespace :account do
+ get ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
+ end
+ end
+
assert_equal '/account/twitter/callback', account_callback_path("twitter")
get '/account/twitter/callback'
assert_equal 'account/callbacks#twitter', @response.body
@@ -1322,23 +1163,39 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_convention_match_nested_and_with_leading_slash
+ draw do
+ get '/account/nested/overview'
+ end
+
assert_equal '/account/nested/overview', account_nested_overview_path
get '/account/nested/overview'
assert_equal 'account/nested#overview', @response.body
end
def test_convention_with_explicit_end
+ draw do
+ get 'sign_in' => "sessions#new"
+ end
+
get '/sign_in'
assert_equal 'sessions#new', @response.body
assert_equal '/sign_in', sign_in_path
end
def test_redirect_with_complete_url_and_status
+ draw do
+ get 'account/google' => redirect('http://www.google.com/', :status => 302)
+ end
+
get '/account/google'
verify_redirect 'http://www.google.com/', 302
end
def test_redirect_with_port
+ draw do
+ get 'account/login', :to => redirect("/login")
+ end
+
previous_host, self.host = self.host, 'www.example.com:3000'
get '/account/login'
@@ -1348,6 +1205,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_normalize_namespaced_matches
+ draw do
+ namespace :account do
+ get 'description', :to => :description, :as => "description"
+ end
+ end
+
assert_equal '/account/description', account_description_path
get '/account/description'
@@ -1355,18 +1218,36 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_namespaced_roots
+ draw do
+ namespace :account do
+ root :to => "account#index"
+ end
+ end
+
assert_equal '/account', account_root_path
get '/account'
assert_equal 'account/account#index', @response.body
end
def test_optional_scoped_root
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ root :to => 'projects#index'
+ end
+ end
+
assert_equal '/en', root_path("en")
get '/en'
assert_equal 'projects#index', @response.body
end
def test_optional_scoped_path
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ resources :descriptions
+ end
+ end
+
assert_equal '/en/descriptions', descriptions_path("en")
assert_equal '/descriptions', descriptions_path(nil)
assert_equal '/en/descriptions/1', description_path("en", 1)
@@ -1386,6 +1267,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_nested_optional_scoped_path
+ draw do
+ namespace :admin do
+ scope '(:locale)', :locale => /en|pl/ do
+ resources :descriptions
+ end
+ end
+ end
+
assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
assert_equal '/admin/descriptions', admin_descriptions_path(nil)
assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
@@ -1405,6 +1294,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_nested_optional_path_shorthand
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ get "registrations/new"
+ end
+ end
+
get '/registrations/new'
assert_nil @request.params[:locale]
@@ -1413,6 +1308,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_default_params
+ draw do
+ get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
+ get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
+
+ defaults :id => 'home' do
+ get 'scoped_pages/(:id)', :to => 'pages#show'
+ end
+ end
+
get '/inline_pages'
assert_equal 'home', @request.params[:id]
@@ -1424,6 +1328,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resource_constraints
+ draw do
+ resources :products, :constraints => { :id => /\d{4}/ } do
+ root :to => "products#root"
+ get :favorite, :on => :collection
+ resources :images
+ end
+
+ resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
+ end
+
get '/products/1'
assert_equal 'pass', @response.headers['X-Cascade']
get '/products'
@@ -1447,18 +1361,35 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_root_works_in_the_resources_scope
+ draw do
+ resources :products do
+ root :to => "products#root"
+ end
+ end
+
get '/products'
assert_equal 'products#root', @response.body
assert_equal '/products', products_root_path
end
def test_module_scope
+ draw do
+ resource :token, :module => :api
+ end
+
get '/token'
assert_equal 'api/tokens#show', @response.body
assert_equal '/token', token_path
end
def test_path_scope
+ draw do
+ scope :path => 'api' do
+ resource :me
+ get '/' => 'mes#index'
+ end
+ end
+
get '/api/me'
assert_equal 'mes#show', @response.body
assert_equal '/api/me', me_path
@@ -1467,7 +1398,36 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'mes#index', @response.body
end
+ def test_symbol_scope
+ draw do
+ scope :path => 'api' do
+ scope :v2 do
+ resource :me, as: 'v2_me'
+ get '/' => 'mes#index'
+ end
+
+ scope :v3, :admin do
+ resource :me, as: 'v3_me'
+ end
+ end
+ end
+
+ get '/api/v2/me'
+ assert_equal 'mes#show', @response.body
+ assert_equal '/api/v2/me', v2_me_path
+
+ get '/api/v2'
+ assert_equal 'mes#index', @response.body
+
+ get '/api/v3/admin/me'
+ assert_equal 'mes#show', @response.body
+ end
+
def test_url_generator_for_generic_route
+ draw do
+ get "whatever/:controller(/:action(/:id))"
+ end
+
get 'whatever/foo/bar'
assert_equal 'foo#bar', @response.body
@@ -1476,6 +1436,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_url_generator_for_namespaced_generic_route
+ draw do
+ get "whatever/:controller(/:action(/:id))", :id => /\d+/
+ end
+
get 'whatever/foo/bar/show'
assert_equal 'foo/bar#show', @response.body
@@ -1489,11 +1453,37 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
url_for(:controller => "foo/bar", :action => "show", :id => '1')
end
- def test_assert_recognizes_account_overview
- assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
- end
-
def test_resource_new_actions
+ draw do
+ resources :replies do
+ new do
+ post :preview
+ end
+ end
+
+ scope 'pt', :as => 'pt' do
+ resources :projects, :path_names => { :new => 'novo' }, :path => 'projetos' do
+ post :preview, :on => :new
+ end
+
+ resource :admin, :path_names => { :new => 'novo' }, :path => 'administrador' do
+ post :preview, :on => :new
+ end
+
+ resources :products, :path_names => { :new => 'novo' } do
+ new do
+ post :preview
+ end
+ end
+ end
+
+ resource :profile do
+ new do
+ post :preview
+ end
+ end
+ end
+
assert_equal '/replies/new/preview', preview_new_reply_path
assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path
assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path
@@ -1517,13 +1507,27 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resource_merges_options_from_scope
- assert_raise(NameError) { new_account_path }
+ draw do
+ scope :only => :show do
+ resource :account
+ end
+ end
+
+ assert_raise(NoMethodError) { new_account_path }
get '/account/new'
assert_equal 404, status
end
def test_resources_merges_options_from_scope
+ draw do
+ scope :only => [:index, :show] do
+ resources :products do
+ resources :images
+ end
+ end
+ end
+
assert_raise(NoMethodError) { edit_product_path('1') }
get '/products/1/edit'
@@ -1536,6 +1540,28 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_shallow_nested_resources
+ draw do
+ shallow do
+ namespace :api do
+ resources :teams do
+ resources :players
+ resource :captain
+ end
+ end
+ end
+
+ resources :threads, :shallow => true do
+ resource :owner
+ resources :messages do
+ resources :comments do
+ member do
+ post :preview
+ end
+ end
+ end
+ end
+ end
+
get '/api/teams'
assert_equal 'api/teams#index', @response.body
assert_equal '/api/teams', api_teams_path
@@ -1638,6 +1664,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_shallow_nested_resources_within_scope
+ draw do
+ scope '/hello' do
+ shallow do
+ resources :notes do
+ resources :trackbacks
+ end
+ end
+ end
+ end
+
get '/hello/notes/1/trackbacks'
assert_equal 'trackbacks#index', @response.body
assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
@@ -1689,6 +1725,36 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_custom_resource_routes_are_scoped
+ draw do
+ resources :customers do
+ get :recent, :on => :collection
+ get "profile", :on => :member
+ get "secret/profile" => "customers#secret", :on => :member
+ post "preview" => "customers#preview", :as => :another_preview, :on => :new
+ resource :avatar do
+ get "thumbnail" => "avatars#thumbnail", :as => :thumbnail, :on => :member
+ end
+ resources :invoices do
+ get "outstanding" => "invoices#outstanding", :on => :collection
+ get "overdue", :to => :overdue, :on => :collection
+ get "print" => "invoices#print", :as => :print, :on => :member
+ post "preview" => "invoices#preview", :as => :preview, :on => :new
+ end
+ resources :notes, :shallow => true do
+ get "preview" => "notes#preview", :as => :preview, :on => :new
+ get "print" => "notes#print", :as => :print, :on => :member
+ end
+ end
+
+ namespace :api do
+ resources :customers do
+ get "recent" => "customers#recent", :as => :recent, :on => :collection
+ get "profile" => "customers#profile", :as => :profile, :on => :member
+ post "preview" => "customers#preview", :as => :preview, :on => :new
+ end
+ end
+ end
+
assert_equal '/customers/recent', recent_customers_path
assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1')
@@ -1711,6 +1777,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_shallow_nested_routes_ignore_module
+ draw do
+ scope :module => :api do
+ resources :errors, :shallow => true do
+ resources :notices
+ end
+ end
+ end
+
get '/errors/1/notices'
assert_equal 'api/notices#index', @response.body
assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
@@ -1721,6 +1795,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_non_greedy_regexp
+ draw do
+ namespace :api do
+ scope(':version', :version => /.+/) do
+ resources :users, :id => /.+?/, :format => /json|xml/
+ end
+ end
+ end
+
get '/api/1.0/users'
assert_equal 'api/users#index', @response.body
assert_equal '/api/1.0/users', api_users_path(:version => '1.0')
@@ -1743,16 +1825,28 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_glob_parameter_accepts_regexp
+ draw do
+ get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
+ end
+
get '/en/path/to/existing/file.html'
assert_equal 200, @response.status
end
def test_resources_controller_name_is_not_pluralized
+ draw do
+ resources :content
+ end
+
get '/content'
assert_equal 'content#index', @response.body
end
def test_url_generator_for_optional_prefix_dynamic_segment
+ draw do
+ get "(/:username)/followers" => "followers#index"
+ end
+
get '/bob/followers'
assert_equal 'followers#index', @response.body
assert_equal 'http://www.example.com/bob/followers',
@@ -1765,6 +1859,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_url_generator_for_optional_suffix_static_and_dynamic_segment
+ draw do
+ get "/groups(/user/:username)" => "groups#index"
+ end
+
get '/groups/user/bob'
assert_equal 'groups#index', @response.body
assert_equal 'http://www.example.com/groups/user/bob',
@@ -1777,6 +1875,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_url_generator_for_optional_prefix_static_and_dynamic_segment
+ draw do
+ get "(/user/:username)/photos" => "photos#index"
+ end
+
get 'user/bob/photos'
assert_equal 'photos#index', @response.body
assert_equal 'http://www.example.com/user/bob/photos',
@@ -1789,6 +1891,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_url_recognition_for_optional_static_segments
+ draw do
+ scope '(groups)' do
+ scope '(discussions)' do
+ resources :messages
+ end
+ end
+ end
+
get '/groups/discussions/messages'
assert_equal 'messages#index', @response.body
@@ -1815,12 +1925,27 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_router_removes_invalid_conditions
+ draw do
+ scope :constraints => { :id => /\d+/ } do
+ get '/tickets', :to => 'tickets#index', :as => :tickets
+ end
+ end
+
get '/tickets'
assert_equal 'tickets#index', @response.body
assert_equal '/tickets', tickets_path
end
def test_constraints_are_merged_from_scope
+ draw do
+ scope :constraints => { :id => /\d{4}/ } do
+ resources :movies do
+ resources :reviews
+ resource :trailer
+ end
+ end
+ end
+
get '/movies/0001'
assert_equal 'movies#show', @response.body
assert_equal '/movies/0001', movie_path(:id => '0001')
@@ -1855,6 +1980,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_only_should_be_read_from_scope
+ draw do
+ scope :only => [:index, :show] do
+ namespace :only do
+ resources :clubs do
+ resources :players
+ resource :chairman
+ end
+ end
+ end
+ end
+
get '/only/clubs'
assert_equal 'only/clubs#index', @response.body
assert_equal '/only/clubs', only_clubs_path
@@ -1881,6 +2017,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_except_should_be_read_from_scope
+ draw do
+ scope :except => [:new, :create, :edit, :update, :destroy] do
+ namespace :except do
+ resources :clubs do
+ resources :players
+ resource :chairman
+ end
+ end
+ end
+ end
+
get '/except/clubs'
assert_equal 'except/clubs#index', @response.body
assert_equal '/except/clubs', except_clubs_path
@@ -1907,6 +2054,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_only_option_should_override_scope
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index
+ end
+ end
+ end
+
get '/only/sectors'
assert_equal 'only/sectors#index', @response.body
assert_equal '/only/sectors', only_sectors_path
@@ -1917,6 +2072,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_only_option_should_not_inherit
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :companies
+ resource :leader
+ end
+ end
+ end
+ end
+
get '/only/sectors/1/companies/2'
assert_equal 'only/companies#show', @response.body
assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2')
@@ -1927,6 +2093,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_except_option_should_override_scope
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy]
+ end
+ end
+ end
+
get '/except/sectors'
assert_equal 'except/sectors#index', @response.body
assert_equal '/except/sectors', except_sectors_path
@@ -1937,6 +2111,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_except_option_should_not_inherit
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :companies
+ resource :leader
+ end
+ end
+ end
+ end
+
get '/except/sectors/1/companies/2'
assert_equal 'except/companies#show', @response.body
assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2')
@@ -1947,6 +2132,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_except_option_should_override_scoped_only
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :managers, :except => [:show, :update, :destroy]
+ end
+ end
+ end
+ end
+
get '/only/sectors/1/managers'
assert_equal 'only/managers#index', @response.body
assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1')
@@ -1957,6 +2152,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_only_option_should_override_scoped_except
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :managers, :only => :index
+ end
+ end
+ end
+ end
+
get '/except/sectors/1/managers'
assert_equal 'except/managers#index', @response.body
assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1')
@@ -1967,6 +2172,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_only_scope_should_override_parent_scope
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :companies do
+ scope :only => :index do
+ resources :divisions
+ end
+ end
+ end
+ end
+ end
+ end
+
get '/only/sectors/1/companies/2/divisions'
assert_equal 'only/divisions#index', @response.body
assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
@@ -1977,6 +2196,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_except_scope_should_override_parent_scope
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :companies do
+ scope :except => [:show, :update, :destroy] do
+ resources :divisions
+ end
+ end
+ end
+ end
+ end
+ end
+
get '/except/sectors/1/companies/2/divisions'
assert_equal 'except/divisions#index', @response.body
assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
@@ -1987,6 +2220,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_except_scope_should_override_parent_only_scope
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :companies do
+ scope :except => [:show, :update, :destroy] do
+ resources :departments
+ end
+ end
+ end
+ end
+ end
+ end
+
get '/only/sectors/1/companies/2/departments'
assert_equal 'only/departments#index', @response.body
assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2')
@@ -1997,6 +2244,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_only_scope_should_override_parent_except_scope
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :companies do
+ scope :only => :index do
+ resources :departments
+ end
+ end
+ end
+ end
+ end
+ end
+
get '/except/sectors/1/companies/2/departments'
assert_equal 'except/departments#index', @response.body
assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2')
@@ -2007,6 +2268,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resources_are_not_pluralized
+ draw do
+ namespace :transport do
+ resources :taxis
+ end
+ end
+
get '/transport/taxis'
assert_equal 'transport/taxis#index', @response.body
assert_equal '/transport/taxis', transport_taxis_path
@@ -2034,6 +2301,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_singleton_resources_are_not_singularized
+ draw do
+ namespace :medical do
+ resource :taxis
+ end
+ end
+
get '/medical/taxis/new'
assert_equal 'medical/taxis#new', @response.body
assert_equal '/medical/taxis/new', new_medical_taxis_path
@@ -2057,6 +2330,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_greedy_resource_id_regexp_doesnt_match_edit_and_custom_action
+ draw do
+ resources :sections, :id => /.+/ do
+ get :preview, :on => :member
+ end
+ end
+
get '/sections/1/edit'
assert_equal 'sections#edit', @response.body
assert_equal '/sections/1/edit', edit_section_path(:id => '1')
@@ -2067,6 +2346,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resource_constraints_are_pushed_to_scope
+ draw do
+ namespace :wiki do
+ resources :articles, :id => /[^\/]+/ do
+ resources :comments, :only => [:create, :new]
+ end
+ end
+ end
+
get '/wiki/articles/Ruby_on_Rails_3.0'
assert_equal 'wiki/articles#show', @response.body
assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0')
@@ -2081,6 +2368,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_resources_path_can_be_a_symbol
+ draw do
+ resources :wiki_pages, :path => :pages
+ resource :wiki_account, :path => :my_account
+ end
+
get '/pages'
assert_equal 'wiki_pages#index', @response.body
assert_equal '/pages', wiki_pages_path
@@ -2095,6 +2387,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_redirect_https
+ draw do
+ get 'secure', :to => redirect("/secure/login")
+ end
+
with_https do
get '/secure'
verify_redirect 'https://www.example.com/secure/login'
@@ -2102,6 +2398,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_symbolized_path_parameters_is_not_stale
+ draw do
+ scope '/countries/:country', :constraints => lambda { |params, req| %w(all France).include?(params[:country]) } do
+ get '/', :to => 'countries#index'
+ get '/cities', :to => 'countries#cities'
+ end
+
+ get '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
+ end
+
get '/countries/France'
assert_equal 'countries#index', @response.body
@@ -2116,6 +2421,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_constraints_block_not_carried_to_following_routes
+ draw do
+ scope '/italians' do
+ get '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
+ get '/sculptors', :to => 'italians#sculptors'
+ get '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
+ end
+ end
+
get '/italians/writers'
assert_equal 'Not Found', @response.body
@@ -2130,6 +2443,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_custom_resource_actions_defined_using_string
+ draw do
+ resources :customers do
+ resources :invoices do
+ get "aged/:months", :on => :collection, :action => :aged, :as => :aged
+ end
+
+ get "inactive", :on => :collection
+ post "deactivate", :on => :member
+ get "old", :on => :collection, :as => :stale
+ end
+ end
+
get '/customers/inactive'
assert_equal 'customers#inactive', @response.body
assert_equal '/customers/inactive', inactive_customers_path
@@ -2148,18 +2473,38 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_route_defined_in_resources_scope_level
+ draw do
+ resources :customers do
+ get "export"
+ end
+ end
+
get '/customers/1/export'
assert_equal 'customers#export', @response.body
assert_equal '/customers/1/export', customer_export_path(:customer_id => '1')
end
def test_named_character_classes_in_regexp_constraints
+ draw do
+ get '/purchases/:token/:filename',
+ :to => 'purchases#fetch',
+ :token => /[[:alnum:]]{10}/,
+ :filename => /(.+)/,
+ :as => :purchase
+ end
+
get '/purchases/315004be7e/Ruby_on_Rails_3.pdf'
assert_equal 'purchases#fetch', @response.body
assert_equal '/purchases/315004be7e/Ruby_on_Rails_3.pdf', purchase_path(:token => '315004be7e', :filename => 'Ruby_on_Rails_3.pdf')
end
def test_nested_resource_constraints
+ draw do
+ resources :lists, :id => /([A-Za-z0-9]{25})|default/ do
+ resources :todos, :id => /\d+/
+ end
+ end
+
get '/lists/01234012340123401234fffff'
assert_equal 'lists#show', @response.body
assert_equal '/lists/01234012340123401234fffff', list_path(:id => '01234012340123401234fffff')
@@ -2174,6 +2519,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_named_routes_collision_is_avoided_unless_explicitly_given_as
+ draw do
+ scope :as => "routes" do
+ get "/c/:id", :as => :collision, :to => "collision#show"
+ get "/collision", :to => "collision#show"
+ get "/no_collision", :to => "collision#show", :as => nil
+
+ get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
+ get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
+ end
+ end
+
assert_equal "/c/1", routes_collision_path(1)
assert_equal "/fc/1", routes_forced_collision_path(1)
end
@@ -2184,86 +2540,100 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_explicitly_avoiding_the_named_route
+ draw do
+ scope :as => "routes" do
+ get "/c/:id", :as => :collision, :to => "collision#show"
+ get "/collision", :to => "collision#show"
+ get "/no_collision", :to => "collision#show", :as => nil
+
+ get "/fc/:id", :as => :forced_collision, :to => "forced_collision#show"
+ get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
+ end
+ end
+
assert !respond_to?(:routes_no_collision_path)
end
def test_controller_name_with_leading_slash_raise_error
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/feeds/:service', :to => '/feeds#show' }
- end
+ draw { get '/feeds/:service', :to => '/feeds#show' }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/feeds/:service', :controller => '/feeds', :action => 'show' }
- end
+ draw { get '/feeds/:service', :controller => '/feeds', :action => 'show' }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/api/feeds/:service', :to => '/api/feeds#show' }
- end
+ draw { get '/api/feeds/:service', :to => '/api/feeds#show' }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
- end
+ draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { resources :feeds, :controller => '/feeds' }
- end
+ draw { resources :feeds, :controller => '/feeds' }
end
end
def test_invalid_route_name_raises_error
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/products', :to => 'products#index', :as => 'products ' }
- end
+ draw { get '/products', :to => 'products#index', :as => 'products ' }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/products', :to => 'products#index', :as => ' products' }
- end
+ draw { get '/products', :to => 'products#index', :as => ' products' }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/products', :to => 'products#index', :as => 'products!' }
- end
+ draw { get '/products', :to => 'products#index', :as => 'products!' }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/products', :to => 'products#index', :as => 'products index' }
- end
+ draw { get '/products', :to => 'products#index', :as => 'products index' }
end
assert_raise(ArgumentError) do
- self.class.stub_controllers do |routes|
- routes.draw { get '/products', :to => 'products#index', :as => '1products' }
- end
+ draw { get '/products', :to => 'products#index', :as => '1products' }
end
end
def test_nested_route_in_nested_resource
+ draw do
+ resources :posts, :only => [:index, :show] do
+ resources :comments, :except => :destroy do
+ get "views" => "comments#views", :as => :views
+ end
+ end
+ end
+
get "/posts/1/comments/2/views"
assert_equal "comments#views", @response.body
assert_equal "/posts/1/comments/2/views", post_comment_views_path(:post_id => '1', :comment_id => '2')
end
def test_root_in_deeply_nested_scope
+ draw do
+ resources :posts, :only => [:index, :show] do
+ namespace :admin do
+ root :to => "index#index"
+ end
+ end
+ end
+
get "/posts/1/admin"
assert_equal "admin/index#index", @response.body
assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1')
end
def test_custom_param
+ draw do
+ resources :profiles, :param => :username do
+ get :details, :on => :member
+ resources :messages
+ end
+ end
+
get '/profiles/bob'
assert_equal 'profiles#show', @response.body
assert_equal 'bob', @request.params[:username]
@@ -2277,6 +2647,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_custom_param_constraint
+ draw do
+ resources :profiles, :param => :username, :username => /[a-z]+/ do
+ get :details, :on => :member
+ resources :messages
+ end
+ end
+
get '/profiles/bob1'
assert_equal 404, @response.status
@@ -2288,12 +2665,41 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_shallow_custom_param
+ draw do
+ resources :orders do
+ constraints :download => /[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}/ do
+ resources :downloads, :param => :download, :shallow => true
+ end
+ end
+ end
+
get '/downloads/0c0c0b68-d24b-11e1-a861-001ff3fffe6f.zip'
assert_equal 'downloads#show', @response.body
assert_equal '0c0c0b68-d24b-11e1-a861-001ff3fffe6f', @request.params[:download]
end
private
+
+ def draw(&block)
+ self.class.stub_controllers do |routes|
+ @app = routes
+ @app.default_url_options = { host: 'www.example.com' }
+ @app.draw(&block)
+ end
+ end
+
+ def url_for(options = {})
+ @app.url_helpers.url_for(options)
+ end
+
+ def method_missing(method, *args, &block)
+ if method.to_s =~ /_(path|url)$/
+ @app.url_helpers.send(method, *args, &block)
+ else
+ super
+ end
+ end
+
def with_https
old_https = https?
https!
@@ -2313,6 +2719,67 @@ private
end
end
+class TestAltApp < ActionDispatch::IntegrationTest
+ class AltRequest
+ def initialize(env)
+ @env = env
+ end
+
+ def path_info
+ "/"
+ end
+
+ def request_method
+ "GET"
+ end
+
+ def ip
+ "127.0.0.1"
+ end
+
+ def x_header
+ @env["HTTP_X_HEADER"] || ""
+ end
+ end
+
+ class XHeader
+ def call(env)
+ [200, {"Content-Type" => "text/html"}, ["XHeader"]]
+ end
+ end
+
+ class AltApp
+ def call(env)
+ [200, {"Content-Type" => "text/html"}, ["Alternative App"]]
+ end
+ end
+
+ AltRoutes = ActionDispatch::Routing::RouteSet.new(AltRequest)
+ AltRoutes.draw do
+ get "/" => TestAltApp::XHeader.new, :constraints => {:x_header => /HEADER/}
+ get "/" => TestAltApp::AltApp.new
+ end
+
+ def app
+ AltRoutes
+ end
+
+ def test_alt_request_without_header
+ get "/"
+ assert_equal "Alternative App", @response.body
+ end
+
+ def test_alt_request_with_matched_header
+ get "/", {}, "HTTP_X_HEADER" => "HEADER"
+ assert_equal "XHeader", @response.body
+ end
+
+ def test_alt_request_with_unmatched_header
+ get "/", {}, "HTTP_X_HEADER" => "NON_MATCH"
+ assert_equal "Alternative App", @response.body
+ end
+end
+
class TestAppendingRoutes < ActionDispatch::IntegrationTest
def simple_app(resp)
lambda { |e| [ 200, { 'Content-Type' => 'text/plain' }, [resp] ] }
@@ -2598,6 +3065,35 @@ class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest
end
end
+class TestGlobRoutingMapper < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/*id" => redirect("/not_cars"), :constraints => {id: /dummy/}
+ get "/cars" => ok
+ end
+ end
+
+ #include Routes.url_helpers
+ def app; Routes end
+
+ def test_glob_constraint
+ get "/dummy"
+ assert_equal "301", @response.code
+ assert_equal "/not_cars", @response.header['Location'].match('/[^/]+$')[0]
+ end
+
+ def test_glob_constraint_skip_route
+ get "/cars"
+ assert_equal "200", @response.code
+ end
+ def test_glob_constraint_skip_all
+ get "/missing"
+ assert_equal "404", @response.code
+ end
+end
+
class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb
index 8daf3d3f5e..fe1a7b4f86 100644
--- a/actionpack/test/dispatch/session/abstract_store_test.rb
+++ b/actionpack/test/dispatch/session/abstract_store_test.rb
@@ -42,7 +42,7 @@ module ActionDispatch
as.call(@env)
session1 = Request::Session.find @env
- refute_equal session, session1
+ assert_not_equal session, session1
assert_equal session.to_hash, session1.to_hash
end
diff --git a/actionpack/test/dispatch/spec_type_test.rb b/actionpack/test/dispatch/spec_type_test.rb
deleted file mode 100644
index 6cd19fd333..0000000000
--- a/actionpack/test/dispatch/spec_type_test.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require "abstract_unit"
-
-class SpecTypeTest < ActiveSupport::TestCase
- def assert_dispatch actual
- assert_equal ActionDispatch::IntegrationTest, actual
- end
-
- def refute_dispatch actual
- refute_equal ActionDispatch::IntegrationTest, actual
- end
-
- def test_spec_type_resolves_for_matching_acceptance_strings
- assert_dispatch MiniTest::Spec.spec_type("WidgetAcceptanceTest")
- assert_dispatch MiniTest::Spec.spec_type("Widget Acceptance Test")
- assert_dispatch MiniTest::Spec.spec_type("widgetacceptancetest")
- assert_dispatch MiniTest::Spec.spec_type("widget acceptance test")
- end
-
- def test_spec_type_wont_match_non_space_characters_acceptance
- refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\tTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\rTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\nTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\fTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget AcceptanceXTest")
- end
-
- def test_spec_type_resolves_for_matching_integration_strings
- assert_dispatch MiniTest::Spec.spec_type("WidgetIntegrationTest")
- assert_dispatch MiniTest::Spec.spec_type("Widget Integration Test")
- assert_dispatch MiniTest::Spec.spec_type("widgetintegrationtest")
- assert_dispatch MiniTest::Spec.spec_type("widget integration test")
- end
-
- def test_spec_type_wont_match_non_space_characters_integration
- refute_dispatch MiniTest::Spec.spec_type("Widget Integration\tTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget Integration\rTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget Integration\nTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget Integration\fTest")
- refute_dispatch MiniTest::Spec.spec_type("Widget IntegrationXTest")
- end
-end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index 6f075a9074..a9bea7ea73 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -47,7 +47,7 @@ class SSLTest < ActionDispatch::IntegrationTest
def test_disable_hsts_header
self.app = ActionDispatch::SSL.new(default_app, :hsts => false)
get "https://example.org/"
- refute response.headers['Strict-Transport-Security']
+ assert_not response.headers['Strict-Transport-Security']
end
def test_hsts_expires
@@ -57,6 +57,13 @@ class SSLTest < ActionDispatch::IntegrationTest
response.headers['Strict-Transport-Security']
end
+ def test_hsts_expires_with_duration
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 1.year })
+ get "https://example.org/"
+ assert_equal "max-age=31557600",
+ response.headers['Strict-Transport-Security']
+ end
+
def test_hsts_include_subdomains
self.app = ActionDispatch::SSL.new(default_app, :hsts => { :subdomains => true })
get "https://example.org/"
diff --git a/actionpack/test/fixtures/digestor/messages/show.html.erb b/actionpack/test/fixtures/digestor/messages/show.html.erb
index 9f73345a9f..51b3b61e8e 100644
--- a/actionpack/test/fixtures/digestor/messages/show.html.erb
+++ b/actionpack/test/fixtures/digestor/messages/show.html.erb
@@ -6,4 +6,8 @@
<%= render @message.history.events %>
-<%# render "something_missing" %> \ No newline at end of file
+<%# render "something_missing" %>
+
+<%
+ # Template Dependency: messages/form
+%> \ No newline at end of file
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
new file mode 100644
index 0000000000..3125583a28
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
@@ -0,0 +1,3 @@
+<body>
+<%= cache 'nodigest', skip_digest: true do %><p>ERB</p><% end %>
+</body>
diff --git a/actionpack/test/journey/gtg/builder_test.rb b/actionpack/test/journey/gtg/builder_test.rb
new file mode 100644
index 0000000000..c1da374007
--- /dev/null
+++ b/actionpack/test/journey/gtg/builder_test.rb
@@ -0,0 +1,79 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module GTG
+ class TestBuilder < ActiveSupport::TestCase
+ def test_following_states_multi
+ table = tt ['a|a']
+ assert_equal 1, table.move([0], 'a').length
+ end
+
+ def test_following_states_multi_regexp
+ table = tt [':a|b']
+ assert_equal 1, table.move([0], 'fooo').length
+ assert_equal 2, table.move([0], 'b').length
+ end
+
+ def test_multi_path
+ table = tt ['/:a/d', '/b/c']
+
+ [
+ [1, '/'],
+ [2, 'b'],
+ [2, '/'],
+ [1, 'c'],
+ ].inject([0]) { |state, (exp, sym)|
+ new = table.move(state, sym)
+ assert_equal exp, new.length
+ new
+ }
+ end
+
+ def test_match_data_ambiguous
+ table = tt %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+
+ sim = NFA::Simulator.new table
+
+ match = sim.match '/articles/new'
+ assert_equal 2, match.memos.length
+ end
+
+ ##
+ # Identical Routes may have different restrictions.
+ def test_match_same_paths
+ table = tt %w{
+ /articles/new(.:format)
+ /articles/new(.:format)
+ }
+
+ sim = NFA::Simulator.new table
+
+ match = sim.match '/articles/new'
+ assert_equal 2, match.memos.length
+ end
+
+ private
+ def ast strings
+ parser = Journey::Parser.new
+ asts = strings.map { |string|
+ memo = Object.new
+ ast = parser.parse string
+ ast.each { |n| n.memo = memo }
+ ast
+ }
+ Nodes::Or.new asts
+ end
+
+ def tt strings
+ Builder.new(ast(strings)).transition_table
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb
new file mode 100644
index 0000000000..33acba8b65
--- /dev/null
+++ b/actionpack/test/journey/gtg/transition_table_test.rb
@@ -0,0 +1,115 @@
+require 'abstract_unit'
+require 'json'
+
+module ActionDispatch
+ module Journey
+ module GTG
+ class TestGeneralizedTable < ActiveSupport::TestCase
+ def test_to_json
+ table = tt %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+
+ json = JSON.load table.to_json
+ assert json['regexp_states']
+ assert json['string_states']
+ assert json['accepting']
+ end
+
+ if system("dot -V 2>/dev/null")
+ def test_to_svg
+ table = tt %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+ svg = table.to_svg
+ assert svg
+ assert_no_match(/DOCTYPE/, svg)
+ end
+ end
+
+ def test_simulate_gt
+ sim = simulator_for ['/foo', '/bar']
+ assert_match sim, '/foo'
+ end
+
+ def test_simulate_gt_regexp
+ sim = simulator_for [':foo']
+ assert_match sim, 'foo'
+ end
+
+ def test_simulate_gt_regexp_mix
+ sim = simulator_for ['/get', '/:method/foo']
+ assert_match sim, '/get'
+ assert_match sim, '/get/foo'
+ end
+
+ def test_simulate_optional
+ sim = simulator_for ['/foo(/bar)']
+ assert_match sim, '/foo'
+ assert_match sim, '/foo/bar'
+ assert_no_match sim, '/foo/'
+ end
+
+ def test_match_data
+ path_asts = asts %w{ /get /:method/foo }
+ paths = path_asts.dup
+
+ builder = GTG::Builder.new Nodes::Or.new path_asts
+ tt = builder.transition_table
+
+ sim = GTG::Simulator.new tt
+
+ match = sim.match '/get'
+ assert_equal [paths.first], match.memos
+
+ match = sim.match '/get/foo'
+ assert_equal [paths.last], match.memos
+ end
+
+ def test_match_data_ambiguous
+ path_asts = asts %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+
+ paths = path_asts.dup
+ ast = Nodes::Or.new path_asts
+
+ builder = GTG::Builder.new ast
+ sim = GTG::Simulator.new builder.transition_table
+
+ match = sim.match '/articles/new'
+ assert_equal [paths[1], paths[3]], match.memos
+ end
+
+ private
+ def asts paths
+ parser = Journey::Parser.new
+ paths.map { |x|
+ ast = parser.parse x
+ ast.each { |n| n.memo = ast}
+ ast
+ }
+ end
+
+ def tt paths
+ x = asts paths
+ builder = GTG::Builder.new Nodes::Or.new x
+ builder.transition_table
+ end
+
+ def simulator_for paths
+ GTG::Simulator.new tt(paths)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/nfa/simulator_test.rb b/actionpack/test/journey/nfa/simulator_test.rb
new file mode 100644
index 0000000000..673a491fe5
--- /dev/null
+++ b/actionpack/test/journey/nfa/simulator_test.rb
@@ -0,0 +1,98 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module NFA
+ class TestSimulator < ActiveSupport::TestCase
+ def test_simulate_simple
+ sim = simulator_for ['/foo']
+ assert_match sim, '/foo'
+ end
+
+ def test_simulate_simple_no_match
+ sim = simulator_for ['/foo']
+ assert_no_match sim, 'foo'
+ end
+
+ def test_simulate_simple_no_match_too_long
+ sim = simulator_for ['/foo']
+ assert_no_match sim, '/foo/bar'
+ end
+
+ def test_simulate_simple_no_match_wrong_string
+ sim = simulator_for ['/foo']
+ assert_no_match sim, '/bar'
+ end
+
+ def test_simulate_regex
+ sim = simulator_for ['/:foo/bar']
+ assert_match sim, '/bar/bar'
+ assert_match sim, '/foo/bar'
+ end
+
+ def test_simulate_or
+ sim = simulator_for ['/foo', '/bar']
+ assert_match sim, '/bar'
+ assert_match sim, '/foo'
+ assert_no_match sim, '/baz'
+ end
+
+ def test_simulate_optional
+ sim = simulator_for ['/foo(/bar)']
+ assert_match sim, '/foo'
+ assert_match sim, '/foo/bar'
+ assert_no_match sim, '/foo/'
+ end
+
+ def test_matchdata_has_memos
+ paths = %w{ /foo /bar }
+ parser = Journey::Parser.new
+ asts = paths.map { |x|
+ ast = parser.parse x
+ ast.each { |n| n.memo = ast}
+ ast
+ }
+
+ expected = asts.first
+
+ builder = Builder.new Nodes::Or.new asts
+
+ sim = Simulator.new builder.transition_table
+
+ md = sim.match '/foo'
+ assert_equal [expected], md.memos
+ end
+
+ def test_matchdata_memos_on_merge
+ parser = Journey::Parser.new
+ routes = [
+ '/articles(.:format)',
+ '/articles/new(.:format)',
+ '/articles/:id/edit(.:format)',
+ '/articles/:id(.:format)',
+ ].map { |path|
+ ast = parser.parse path
+ ast.each { |n| n.memo = ast }
+ ast
+ }
+
+ asts = routes.dup
+
+ ast = Nodes::Or.new routes
+
+ nfa = Journey::NFA::Builder.new ast
+ sim = Simulator.new nfa.transition_table
+ md = sim.match '/articles'
+ assert_equal [asts.first], md.memos
+ end
+
+ def simulator_for paths
+ parser = Journey::Parser.new
+ asts = paths.map { |x| parser.parse x }
+ builder = Builder.new Nodes::Or.new asts
+ Simulator.new builder.transition_table
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/nfa/transition_table_test.rb b/actionpack/test/journey/nfa/transition_table_test.rb
new file mode 100644
index 0000000000..1248082c03
--- /dev/null
+++ b/actionpack/test/journey/nfa/transition_table_test.rb
@@ -0,0 +1,72 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module NFA
+ class TestTransitionTable < ActiveSupport::TestCase
+ def setup
+ @parser = Journey::Parser.new
+ end
+
+ def test_eclosure
+ table = tt '/'
+ assert_equal [0], table.eclosure(0)
+
+ table = tt ':a|:b'
+ assert_equal 3, table.eclosure(0).length
+
+ table = tt '(:a|:b)'
+ assert_equal 5, table.eclosure(0).length
+ assert_equal 5, table.eclosure([0]).length
+ end
+
+ def test_following_states_one
+ table = tt '/'
+
+ assert_equal [1], table.following_states(0, '/')
+ assert_equal [1], table.following_states([0], '/')
+ end
+
+ def test_following_states_group
+ table = tt 'a|b'
+ states = table.eclosure 0
+
+ assert_equal 1, table.following_states(states, 'a').length
+ assert_equal 1, table.following_states(states, 'b').length
+ end
+
+ def test_following_states_multi
+ table = tt 'a|a'
+ states = table.eclosure 0
+
+ assert_equal 2, table.following_states(states, 'a').length
+ assert_equal 0, table.following_states(states, 'b').length
+ end
+
+ def test_following_states_regexp
+ table = tt 'a|:a'
+ states = table.eclosure 0
+
+ assert_equal 1, table.following_states(states, 'a').length
+ assert_equal 1, table.following_states(states, /[^\.\/\?]+/).length
+ assert_equal 0, table.following_states(states, 'b').length
+ end
+
+ def test_alphabet
+ table = tt 'a|:a'
+ assert_equal [/[^\.\/\?]+/, 'a'], table.alphabet
+
+ table = tt 'a|a'
+ assert_equal ['a'], table.alphabet
+ end
+
+ private
+ def tt string
+ ast = @parser.parse string
+ builder = Builder.new ast
+ builder.transition_table
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/nodes/symbol_test.rb b/actionpack/test/journey/nodes/symbol_test.rb
new file mode 100644
index 0000000000..d411a5018a
--- /dev/null
+++ b/actionpack/test/journey/nodes/symbol_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Nodes
+ class TestSymbol < ActiveSupport::TestCase
+ def test_default_regexp?
+ sym = Symbol.new nil
+ assert sym.default_regexp?
+
+ sym.regexp = nil
+ assert_not sym.default_regexp?
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
new file mode 100644
index 0000000000..2b7227cd0d
--- /dev/null
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -0,0 +1,284 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Path
+ class TestPattern < ActiveSupport::TestCase
+ x = /.+/
+ {
+ '/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?\Z},
+ '/:controller/foo' => %r{\A/(#{x})/foo\Z},
+ '/:controller/:action' => %r{\A/(#{x})/([^/.?]+)\Z},
+ '/:controller' => %r{\A/(#{x})\Z},
+ '/:controller(/:action(/:id))' => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z},
+ '/:controller/:action.xml' => %r{\A/(#{x})/([^/.?]+)\.xml\Z},
+ '/:controller.:format' => %r{\A/(#{x})\.([^/.?]+)\Z},
+ '/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?\Z},
+ '/:controller/*foo' => %r{\A/(#{x})/(.+)\Z},
+ '/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar\Z},
+ }.each do |path, expected|
+ define_method(:"test_to_regexp_#{path}") do
+ strexp = Router::Strexp.new(
+ path,
+ { :controller => /.+/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_equal(expected, path.to_regexp)
+ end
+ end
+
+ {
+ '/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?},
+ '/:controller/foo' => %r{\A/(#{x})/foo},
+ '/:controller/:action' => %r{\A/(#{x})/([^/.?]+)},
+ '/:controller' => %r{\A/(#{x})},
+ '/:controller(/:action(/:id))' => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?},
+ '/:controller/:action.xml' => %r{\A/(#{x})/([^/.?]+)\.xml},
+ '/:controller.:format' => %r{\A/(#{x})\.([^/.?]+)},
+ '/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?},
+ '/:controller/*foo' => %r{\A/(#{x})/(.+)},
+ '/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar},
+ }.each do |path, expected|
+ define_method(:"test_to_non_anchored_regexp_#{path}") do
+ strexp = Router::Strexp.new(
+ path,
+ { :controller => /.+/ },
+ ["/", ".", "?"],
+ false
+ )
+ path = Pattern.new strexp
+ assert_equal(expected, path.to_regexp)
+ end
+ end
+
+ {
+ '/:controller(/:action)' => %w{ controller action },
+ '/:controller/foo' => %w{ controller },
+ '/:controller/:action' => %w{ controller action },
+ '/:controller' => %w{ controller },
+ '/:controller(/:action(/:id))' => %w{ controller action id },
+ '/:controller/:action.xml' => %w{ controller action },
+ '/:controller.:format' => %w{ controller format },
+ '/:controller(.:format)' => %w{ controller format },
+ '/:controller/*foo' => %w{ controller foo },
+ '/:controller/*foo/bar' => %w{ controller foo },
+ }.each do |path, expected|
+ define_method(:"test_names_#{path}") do
+ strexp = Router::Strexp.new(
+ path,
+ { :controller => /.+/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_equal(expected, path.names)
+ end
+ end
+
+ def test_to_regexp_with_extended_group
+ strexp = Router::Strexp.new(
+ '/page/:name',
+ { :name => /
+ #ROFL
+ (tender|love
+ #MAO
+ )/x },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/page/tender')
+ assert_match(path, '/page/love')
+ assert_no_match(path, '/page/loving')
+ end
+
+ def test_optional_names
+ [
+ ['/:foo(/:bar(/:baz))', %w{ bar baz }],
+ ['/:foo(/:bar)', %w{ bar }],
+ ['/:foo(/:bar)/:lol(/:baz)', %w{ bar baz }],
+ ].each do |pattern, list|
+ path = Pattern.new pattern
+ assert_equal list.sort, path.optional_names.sort
+ end
+ end
+
+ def test_to_regexp_match_non_optional
+ strexp = Router::Strexp.new(
+ '/:name',
+ { :name => /\d+/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/123')
+ assert_no_match(path, '/')
+ end
+
+ def test_to_regexp_with_group
+ strexp = Router::Strexp.new(
+ '/page/:name',
+ { :name => /(tender|love)/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/page/tender')
+ assert_match(path, '/page/love')
+ assert_no_match(path, '/page/loving')
+ end
+
+ def test_ast_sets_regular_expressions
+ requirements = { :name => /(tender|love)/, :value => /./ }
+ strexp = Router::Strexp.new(
+ '/page/:name/:value',
+ requirements,
+ ["/", ".", "?"]
+ )
+
+ assert_equal requirements, strexp.requirements
+
+ path = Pattern.new strexp
+ nodes = path.ast.grep(Nodes::Symbol)
+ assert_equal 2, nodes.length
+ nodes.each do |node|
+ assert_equal requirements[node.to_sym], node.regexp
+ end
+ end
+
+ def test_match_data_with_group
+ strexp = Router::Strexp.new(
+ '/page/:name',
+ { :name => /(tender|love)/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ match = path.match '/page/tender'
+ assert_equal 'tender', match[1]
+ assert_equal 2, match.length
+ end
+
+ def test_match_data_with_multi_group
+ strexp = Router::Strexp.new(
+ '/page/:name/:id',
+ { :name => /t(((ender|love)))()/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ match = path.match '/page/tender/10'
+ assert_equal 'tender', match[1]
+ assert_equal '10', match[2]
+ assert_equal 3, match.length
+ assert_equal %w{ tender 10 }, match.captures
+ end
+
+ def test_star_with_custom_re
+ z = /\d+/
+ strexp = Router::Strexp.new(
+ '/page/*foo',
+ { :foo => z },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_equal(%r{\A/page/(#{z})\Z}, path.to_regexp)
+ end
+
+ def test_insensitive_regexp_with_group
+ strexp = Router::Strexp.new(
+ '/page/:name/aaron',
+ { :name => /(tender|love)/i },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/page/TENDER/aaron')
+ assert_match(path, '/page/loVE/aaron')
+ assert_no_match(path, '/page/loVE/AAron')
+ end
+
+ def test_to_regexp_with_strexp
+ strexp = Router::Strexp.new('/:controller', { }, ["/", ".", "?"])
+ path = Pattern.new strexp
+ x = %r{\A/([^/.?]+)\Z}
+
+ assert_equal(x.source, path.source)
+ end
+
+ def test_to_regexp_defaults
+ path = Pattern.new '/:controller(/:action(/:id))'
+ expected = %r{\A/([^/.?]+)(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z}
+ assert_equal expected, path.to_regexp
+ end
+
+ def test_failed_match
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ uri = 'content'
+
+ assert_not path =~ uri
+ end
+
+ def test_match_controller
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ uri = '/content'
+
+ match = path =~ uri
+ assert_equal %w{ controller action id format }, match.names
+ assert_equal 'content', match[1]
+ assert_nil match[2]
+ assert_nil match[3]
+ assert_nil match[4]
+ end
+
+ def test_match_controller_action
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ uri = '/content/list'
+
+ match = path =~ uri
+ assert_equal %w{ controller action id format }, match.names
+ assert_equal 'content', match[1]
+ assert_equal 'list', match[2]
+ assert_nil match[3]
+ assert_nil match[4]
+ end
+
+ def test_match_controller_action_id
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
+ uri = '/content/list/10'
+
+ match = path =~ uri
+ assert_equal %w{ controller action id format }, match.names
+ assert_equal 'content', match[1]
+ assert_equal 'list', match[2]
+ assert_equal '10', match[3]
+ assert_nil match[4]
+ end
+
+ def test_match_literal
+ path = Path::Pattern.new "/books(/:action(.:format))"
+
+ uri = '/books'
+ match = path =~ uri
+ assert_equal %w{ action format }, match.names
+ assert_nil match[1]
+ assert_nil match[2]
+ end
+
+ def test_match_literal_with_action
+ path = Path::Pattern.new "/books(/:action(.:format))"
+
+ uri = '/books/list'
+ match = path =~ uri
+ assert_equal %w{ action format }, match.names
+ assert_equal 'list', match[1]
+ assert_nil match[2]
+ end
+
+ def test_match_literal_with_action_and_format
+ path = Path::Pattern.new "/books(/:action(.:format))"
+
+ uri = '/books/list.rss'
+ match = path =~ uri
+ assert_equal %w{ action format }, match.names
+ assert_equal 'list', match[1]
+ assert_equal 'rss', match[2]
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/route/definition/parser_test.rb b/actionpack/test/journey/route/definition/parser_test.rb
new file mode 100644
index 0000000000..d7d7172a40
--- /dev/null
+++ b/actionpack/test/journey/route/definition/parser_test.rb
@@ -0,0 +1,110 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Definition
+ class TestParser < ActiveSupport::TestCase
+ def setup
+ @parser = Parser.new
+ end
+
+ def test_slash
+ assert_equal :SLASH, @parser.parse('/').type
+ assert_round_trip '/'
+ end
+
+ def test_segment
+ assert_round_trip '/foo'
+ end
+
+ def test_segments
+ assert_round_trip '/foo/bar'
+ end
+
+ def test_segment_symbol
+ assert_round_trip '/foo/:id'
+ end
+
+ def test_symbol
+ assert_round_trip '/:foo'
+ end
+
+ def test_group
+ assert_round_trip '(/:foo)'
+ end
+
+ def test_groups
+ assert_round_trip '(/:foo)(/:bar)'
+ end
+
+ def test_nested_groups
+ assert_round_trip '(/:foo(/:bar))'
+ end
+
+ def test_dot_symbol
+ assert_round_trip('.:format')
+ end
+
+ def test_dot_literal
+ assert_round_trip('.xml')
+ end
+
+ def test_segment_dot
+ assert_round_trip('/foo.:bar')
+ end
+
+ def test_segment_group_dot
+ assert_round_trip('/foo(.:bar)')
+ end
+
+ def test_segment_group
+ assert_round_trip('/foo(/:action)')
+ end
+
+ def test_segment_groups
+ assert_round_trip('/foo(/:action)(/:bar)')
+ end
+
+ def test_segment_nested_groups
+ assert_round_trip('/foo(/:action(/:bar))')
+ end
+
+ def test_group_followed_by_path
+ assert_round_trip('/foo(/:action)/:bar')
+ end
+
+ def test_star
+ assert_round_trip('*foo')
+ assert_round_trip('/*foo')
+ assert_round_trip('/bar/*foo')
+ assert_round_trip('/bar/(*foo)')
+ end
+
+ def test_or
+ assert_round_trip('a|b')
+ assert_round_trip('a|b|c')
+ assert_round_trip('(a|b)|c')
+ assert_round_trip('a|(b|c)')
+ assert_round_trip('*a|(b|c)')
+ assert_round_trip('*a|:b|c')
+ end
+
+ def test_arbitrary
+ assert_round_trip('/bar/*foo#')
+ end
+
+ def test_literal_dot_paren
+ assert_round_trip "/sprockets.js(.:format)"
+ end
+
+ def test_groups_with_dot
+ assert_round_trip "/(:locale)(.:format)"
+ end
+
+ def assert_round_trip str
+ assert_equal str, @parser.parse(str).to_s
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb
new file mode 100644
index 0000000000..624e6df51a
--- /dev/null
+++ b/actionpack/test/journey/route/definition/scanner_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Definition
+ class TestScanner < ActiveSupport::TestCase
+ def setup
+ @scanner = Scanner.new
+ end
+
+ # /page/:id(/:action)(.:format)
+ def test_tokens
+ [
+ ['/', [[:SLASH, '/']]],
+ ['*omg', [[:STAR, '*omg']]],
+ ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]],
+ ['/~page', [[:SLASH, '/'], [:LITERAL, '~page']]],
+ ['/pa-ge', [[:SLASH, '/'], [:LITERAL, 'pa-ge']]],
+ ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]],
+ ['/(:page)', [
+ [:SLASH, '/'],
+ [:LPAREN, '('],
+ [:SYMBOL, ':page'],
+ [:RPAREN, ')'],
+ ]],
+ ['(/:action)', [
+ [:LPAREN, '('],
+ [:SLASH, '/'],
+ [:SYMBOL, ':action'],
+ [:RPAREN, ')'],
+ ]],
+ ['(())', [[:LPAREN, '('],
+ [:LPAREN, '('], [:RPAREN, ')'], [:RPAREN, ')']]],
+ ['(.:format)', [
+ [:LPAREN, '('],
+ [:DOT, '.'],
+ [:SYMBOL, ':format'],
+ [:RPAREN, ')'],
+ ]],
+ ].each do |str, expected|
+ @scanner.scan_setup str
+ assert_tokens expected, @scanner
+ end
+ end
+
+ def assert_tokens tokens, scanner
+ toks = []
+ while tok = scanner.next_token
+ toks << tok
+ end
+ assert_equal tokens, toks
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
new file mode 100644
index 0000000000..78608a5c6b
--- /dev/null
+++ b/actionpack/test/journey/route_test.rb
@@ -0,0 +1,103 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class TestRoute < ActiveSupport::TestCase
+ def test_initialize
+ app = Object.new
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ defaults = Object.new
+ route = Route.new("name", app, path, {}, defaults)
+
+ assert_equal app, route.app
+ assert_equal path, route.path
+ assert_equal defaults, route.defaults
+ end
+
+ def test_route_adds_itself_as_memo
+ app = Object.new
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ defaults = Object.new
+ route = Route.new("name", app, path, {}, defaults)
+
+ route.ast.grep(Nodes::Terminal).each do |node|
+ assert_equal route, node.memo
+ end
+ end
+
+ def test_ip_address
+ path = Path::Pattern.new '/messages/:id(.:format)'
+ route = Route.new("name", nil, path, {:ip => '192.168.1.1'},
+ { :controller => 'foo', :action => 'bar' })
+ assert_equal '192.168.1.1', route.ip
+ end
+
+ def test_default_ip
+ path = Path::Pattern.new '/messages/:id(.:format)'
+ route = Route.new("name", nil, path, {},
+ { :controller => 'foo', :action => 'bar' })
+ assert_equal(//, route.ip)
+ end
+
+ def test_format_with_star
+ path = Path::Pattern.new '/:controller/*extra'
+ route = Route.new("name", nil, path, {},
+ { :controller => 'foo', :action => 'bar' })
+ assert_equal '/foo/himom', route.format({
+ :controller => 'foo',
+ :extra => 'himom',
+ })
+ end
+
+ def test_connects_all_match
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
+ route = Route.new("name", nil, path, {:action => 'bar'}, { :controller => 'foo' })
+
+ assert_equal '/foo/bar/10', route.format({
+ :controller => 'foo',
+ :action => 'bar',
+ :id => 10
+ })
+ end
+
+ def test_extras_are_not_included_if_optional
+ path = Path::Pattern.new '/page/:id(/:action)'
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
+
+ assert_equal '/page/10', route.format({ :id => 10 })
+ end
+
+ def test_extras_are_not_included_if_optional_with_parameter
+ path = Path::Pattern.new '(/sections/:section)/pages/:id'
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
+
+ assert_equal '/pages/10', route.format({:id => 10})
+ end
+
+ def test_extras_are_not_included_if_optional_parameter_is_nil
+ path = Path::Pattern.new '(/sections/:section)/pages/:id'
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
+
+ assert_equal '/pages/10', route.format({:id => 10, :section => nil})
+ end
+
+ def test_score
+ path = Path::Pattern.new "/page/:id(/:action)(.:format)"
+ specific = Route.new "name", nil, path, {}, {:controller=>"pages", :action=>"show"}
+
+ path = Path::Pattern.new "/:controller(/:action(/:id))(.:format)"
+ generic = Route.new "name", nil, path, {}
+
+ knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
+
+ routes = [specific, generic]
+
+ assert_not_equal specific.score(knowledge), generic.score(knowledge)
+
+ found = routes.sort_by { |r| r.score(knowledge) }.last
+
+ assert_equal specific, found
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/router/strexp_test.rb b/actionpack/test/journey/router/strexp_test.rb
new file mode 100644
index 0000000000..7ccdfb7b4d
--- /dev/null
+++ b/actionpack/test/journey/router/strexp_test.rb
@@ -0,0 +1,32 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class Router
+ class TestStrexp < ActiveSupport::TestCase
+ def test_many_names
+ exp = Strexp.new(
+ "/:controller(/:action(/:id(.:format)))",
+ {:controller=>/.+?/},
+ ["/", ".", "?"],
+ true)
+
+ assert_equal ["controller", "action", "id", "format"], exp.names
+ end
+
+ def test_names
+ {
+ "/bar(.:format)" => %w{ format },
+ ":format" => %w{ format },
+ ":format-" => %w{ format },
+ ":format0" => %w{ format0 },
+ ":format1,:format2" => %w{ format1 format2 },
+ }.each do |string, expected|
+ exp = Strexp.new(string, {}, ["/", ".", "?"])
+ assert_equal expected, exp.names
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
new file mode 100644
index 0000000000..057dc40cca
--- /dev/null
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class Router
+ class TestUtils < ActiveSupport::TestCase
+ def test_path_escape
+ assert_equal "a/b%20c+d", Utils.escape_path("a/b c+d")
+ end
+
+ def test_fragment_escape
+ assert_equal "a/b%20c+d?e", Utils.escape_fragment("a/b c+d?e")
+ end
+
+ def test_uri_unescape
+ assert_equal "a/b c+d", Utils.unescape_uri("a%2Fb%20c+d")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
new file mode 100644
index 0000000000..27bdb0108a
--- /dev/null
+++ b/actionpack/test/journey/router_test.rb
@@ -0,0 +1,575 @@
+# encoding: UTF-8
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class TestRouter < ActiveSupport::TestCase
+ attr_reader :routes
+
+ def setup
+ @routes = Routes.new
+ @router = Router.new(@routes, {})
+ @formatter = Formatter.new(@routes)
+ end
+
+ def test_request_class_reader
+ klass = Object.new
+ router = Router.new(routes, :request_class => klass)
+ assert_equal klass, router.request_class
+ end
+
+ class FakeRequestFeeler < Struct.new(:env, :called)
+ def new env
+ self.env = env
+ self
+ end
+
+ def hello
+ self.called = true
+ 'world'
+ end
+
+ def path_info; env['PATH_INFO']; end
+ def request_method; env['REQUEST_METHOD']; end
+ def ip; env['REMOTE_ADDR']; end
+ end
+
+ def test_dashes
+ router = Router.new(routes, {})
+
+ exp = Router::Strexp.new '/foo-bar-baz', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, {}, {:id => nil}, {}
+
+ env = rails_env 'PATH_INFO' => '/foo-bar-baz'
+ called = false
+ router.recognize(env) do |r, _, params|
+ called = true
+ end
+ assert called
+ end
+
+ def test_unicode
+ router = Router.new(routes, {})
+
+ #match the escaped version of /ほげ
+ exp = Router::Strexp.new '/%E3%81%BB%E3%81%92', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, {}, {:id => nil}, {}
+
+ env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
+ called = false
+ router.recognize(env) do |r, _, params|
+ called = true
+ end
+ assert called
+ end
+
+ def test_request_class_and_requirements_success
+ klass = FakeRequestFeeler.new nil
+ router = Router.new(routes, {:request_class => klass })
+
+ requirements = { :hello => /world/ }
+
+ exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, requirements, {:id => nil}, {}
+
+ env = rails_env 'PATH_INFO' => '/foo/10'
+ router.recognize(env) do |r, _, params|
+ assert_equal({:id => '10'}, params)
+ end
+
+ assert klass.called, 'hello should have been called'
+ assert_equal env.env, klass.env
+ end
+
+ def test_request_class_and_requirements_fail
+ klass = FakeRequestFeeler.new nil
+ router = Router.new(routes, {:request_class => klass })
+
+ requirements = { :hello => /mom/ }
+
+ exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ router.routes.add_route nil, path, requirements, {:id => nil}, {}
+
+ env = rails_env 'PATH_INFO' => '/foo/10'
+ router.recognize(env) do |r, _, params|
+ flunk 'route should not be found'
+ end
+
+ assert klass.called, 'hello should have been called'
+ assert_equal env.env, klass.env
+ end
+
+ class CustomPathRequest < Router::NullReq
+ def path_info
+ env['custom.path_info']
+ end
+ end
+
+ def test_request_class_overrides_path_info
+ router = Router.new(routes, {:request_class => CustomPathRequest })
+
+ exp = Router::Strexp.new '/bar', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, {}, {}, {}
+
+ env = rails_env 'PATH_INFO' => '/foo', 'custom.path_info' => '/bar'
+
+ recognized = false
+ router.recognize(env) do |r, _, params|
+ recognized = true
+ end
+
+ assert recognized, "route should have been recognized"
+ end
+
+ def test_regexp_first_precedence
+ add_routes @router, [
+ Router::Strexp.new("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
+ Router::Strexp.new("/whois/:id(.:format)", {}, ['/', '.', '?'])
+ ]
+
+ env = rails_env 'PATH_INFO' => '/whois/example.com'
+
+ list = []
+ @router.recognize(env) do |r, _, params|
+ list << r
+ end
+ assert_equal 2, list.length
+
+ r = list.first
+
+ assert_equal '/whois/:domain', r.path.spec.to_s
+ end
+
+ def test_required_parts_verified_are_anchored
+ add_routes @router, [
+ Router::Strexp.new("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
+ ]
+
+ assert_raises(Router::RoutingError) do
+ @formatter.generate(:path_info, nil, { :id => '10' }, { })
+ end
+ end
+
+ def test_required_parts_are_verified_when_building
+ add_routes @router, [
+ Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ ]
+
+ path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
+ assert_equal '/foo/10', path
+
+ assert_raises(Router::RoutingError) do
+ @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
+ end
+ end
+
+ def test_only_required_parts_are_verified
+ add_routes @router, [
+ Router::Strexp.new("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
+ ]
+
+ path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
+ assert_equal '/foo/10', path
+
+ path, _ = @formatter.generate(:path_info, nil, { }, { })
+ assert_equal '/foo', path
+
+ path, _ = @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
+ assert_equal '/foo/aa', path
+ end
+
+ def test_knows_what_parts_are_missing_from_named_route
+ route_name = "gorby_thunderhorse"
+ pattern = Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ path = Path::Pattern.new pattern
+ @router.routes.add_route nil, path, {}, {}, route_name
+
+ error = assert_raises(Router::RoutingError) do
+ @formatter.generate(:path_info, route_name, { }, { })
+ end
+
+ assert_match(/required keys: \[:id\]/, error.message)
+ end
+
+ def test_X_Cascade
+ add_routes @router, [ "/messages(.:format)" ]
+ resp = @router.call({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' })
+ assert_equal ['Not Found'], resp.last
+ assert_equal 'pass', resp[1]['X-Cascade']
+ assert_equal 404, resp.first
+ end
+
+ def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
+ strexp = Router::Strexp.new("/", {}, ['/', '.', '?'], false)
+ path = Path::Pattern.new strexp
+ app = lambda { |env| [200, {}, ['success!']] }
+ @router.routes.add_route(app, path, {}, {}, {})
+
+ env = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog')
+ resp = @router.call(env)
+ assert_equal ['success!'], resp.last
+ assert_equal '', env['SCRIPT_NAME']
+ end
+
+ def test_defaults_merge_correctly
+ path = Path::Pattern.new '/foo(/:id)'
+ @router.routes.add_route nil, path, {}, {:id => nil}, {}
+
+ env = rails_env 'PATH_INFO' => '/foo/10'
+ @router.recognize(env) do |r, _, params|
+ assert_equal({:id => '10'}, params)
+ end
+
+ env = rails_env 'PATH_INFO' => '/foo'
+ @router.recognize(env) do |r, _, params|
+ assert_equal({:id => nil}, params)
+ end
+ end
+
+ def test_recognize_with_unbound_regexp
+ add_routes @router, [
+ Router::Strexp.new("/foo", { }, ['/', '.', '?'], false)
+ ]
+
+ env = rails_env 'PATH_INFO' => '/foo/bar'
+
+ @router.recognize(env) { |*_| }
+
+ assert_equal '/foo', env.env['SCRIPT_NAME']
+ assert_equal '/bar', env.env['PATH_INFO']
+ end
+
+ def test_bound_regexp_keeps_path_info
+ add_routes @router, [
+ Router::Strexp.new("/foo", { }, ['/', '.', '?'], true)
+ ]
+
+ env = rails_env 'PATH_INFO' => '/foo'
+
+ before = env.env['SCRIPT_NAME']
+
+ @router.recognize(env) { |*_| }
+
+ assert_equal before, env.env['SCRIPT_NAME']
+ assert_equal '/foo', env.env['PATH_INFO']
+ end
+
+ def test_path_not_found
+ add_routes @router, [
+ "/messages(.:format)",
+ "/messages/new(.:format)",
+ "/messages/:id/edit(.:format)",
+ "/messages/:id(.:format)"
+ ]
+ env = rails_env 'PATH_INFO' => '/messages/unknown/path'
+ yielded = false
+
+ @router.recognize(env) do |*whatever|
+ yielded = true
+ end
+ assert_not yielded
+ end
+
+ def test_required_part_in_recall
+ add_routes @router, [ "/messages/:a/:b" ]
+
+ path, _ = @formatter.generate(:path_info, nil, { :a => 'a' }, { :b => 'b' })
+ assert_equal "/messages/a/b", path
+ end
+
+ def test_splat_in_recall
+ add_routes @router, [ "/*path" ]
+
+ path, _ = @formatter.generate(:path_info, nil, { }, { :path => 'b' })
+ assert_equal "/b", path
+ end
+
+ def test_recall_should_be_used_when_scoring
+ add_routes @router, [
+ "/messages/:action(/:id(.:format))",
+ "/messages/:id(.:format)"
+ ]
+
+ path, _ = @formatter.generate(:path_info, nil, { :id => 10 }, { :action => 'index' })
+ assert_equal "/messages/index/10", path
+ end
+
+ def test_nil_path_parts_are_ignored
+ path = Path::Pattern.new "/:controller(/:action(.:format))"
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ params = { :controller => "tasks", :format => nil }
+ extras = { :action => 'lol' }
+
+ path, _ = @formatter.generate(:path_info, nil, params, extras)
+ assert_equal '/tasks', path
+ end
+
+ def test_generate_slash
+ params = [ [:controller, "tasks"],
+ [:action, "show"] ]
+ str = Router::Strexp.new("/", Hash[params], ['/', '.', '?'], true)
+ path = Path::Pattern.new str
+
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ path, _ = @formatter.generate(:path_info, nil, Hash[params], {})
+ assert_equal '/', path
+ end
+
+ def test_generate_calls_param_proc
+ path = Path::Pattern.new '/:controller(/:action)'
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ parameterized = []
+ params = [ [:controller, "tasks"],
+ [:action, "show"] ]
+
+ @formatter.generate(
+ :path_info,
+ nil,
+ Hash[params],
+ {},
+ lambda { |k,v| parameterized << [k,v]; v })
+
+ assert_equal params.map(&:to_s).sort, parameterized.map(&:to_s).sort
+ end
+
+ def test_generate_id
+ path = Path::Pattern.new '/:controller(/:action)'
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ path, params = @formatter.generate(
+ :path_info, nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
+ assert_equal '/tasks/show', path
+ assert_equal({:id => 1}, params)
+ end
+
+ def test_generate_escapes
+ path = Path::Pattern.new '/:controller(/:action)'
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ path, _ = @formatter.generate(:path_info,
+ nil, { :controller => "tasks",
+ :action => "a/b c+d",
+ }, {})
+ assert_equal '/tasks/a/b%20c+d', path
+ end
+
+ def test_generate_extra_params
+ path = Path::Pattern.new '/:controller(/:action)'
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ path, params = @formatter.generate(:path_info,
+ nil, { :id => 1,
+ :controller => "tasks",
+ :action => "show",
+ :relative_url_root => nil
+ }, {})
+ assert_equal '/tasks/show', path
+ assert_equal({:id => 1, :relative_url_root => nil}, params)
+ end
+
+ def test_generate_uses_recall_if_needed
+ path = Path::Pattern.new '/:controller(/:action(/:id))'
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ path, params = @formatter.generate(:path_info,
+ nil,
+ {:controller =>"tasks", :id => 10},
+ {:action =>"index"})
+ assert_equal '/tasks/index/10', path
+ assert_equal({}, params)
+ end
+
+ def test_generate_with_name
+ path = Path::Pattern.new '/:controller(/:action)'
+ @router.routes.add_route nil, path, {}, {}, {}
+
+ path, params = @formatter.generate(:path_info,
+ "tasks",
+ {:controller=>"tasks"},
+ {:controller=>"tasks", :action=>"index"})
+ assert_equal '/tasks', path
+ assert_equal({}, params)
+ end
+
+ {
+ '/content' => { :controller => 'content' },
+ '/content/list' => { :controller => 'content', :action => 'list' },
+ '/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
+ }.each do |request_path, expected|
+ define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
+ path = Path::Pattern.new "/:controller(/:action(/:id))"
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {}, {})
+
+ env = rails_env 'PATH_INFO' => request_path
+ called = false
+
+ @router.recognize(env) do |r, _, params|
+ assert_equal route, r
+ assert_equal(expected, params)
+ called = true
+ end
+
+ assert called
+ end
+ end
+
+ {
+ :segment => ['/a%2Fb%20c+d/splat', { :segment => 'a/b c+d', :splat => 'splat' }],
+ :splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
+ }.each do |name, (request_path, expected)|
+ define_method("test_recognize_#{name}") do
+ path = Path::Pattern.new '/:segment/*splat'
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {}, {})
+
+ env = rails_env 'PATH_INFO' => request_path
+ called = false
+
+ @router.recognize(env) do |r, _, params|
+ assert_equal route, r
+ assert_equal(expected, params)
+ called = true
+ end
+
+ assert called
+ end
+ end
+
+ def test_namespaced_controller
+ strexp = Router::Strexp.new(
+ "/:controller(/:action(/:id))",
+ { :controller => /.+?/ },
+ ["/", ".", "?"]
+ )
+ path = Path::Pattern.new strexp
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {}, {})
+
+ env = rails_env 'PATH_INFO' => '/admin/users/show/10'
+ called = false
+ expected = {
+ :controller => 'admin/users',
+ :action => 'show',
+ :id => '10'
+ }
+
+ @router.recognize(env) do |r, _, params|
+ assert_equal route, r
+ assert_equal(expected, params)
+ called = true
+ end
+ assert called
+ end
+
+ def test_recognize_literal
+ path = Path::Pattern.new "/books(/:action(.:format))"
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {:controller => 'books'})
+
+ env = rails_env 'PATH_INFO' => '/books/list.rss'
+ expected = { :controller => 'books', :action => 'list', :format => 'rss' }
+ called = false
+ @router.recognize(env) do |r, _, params|
+ assert_equal route, r
+ assert_equal(expected, params)
+ called = true
+ end
+
+ assert called
+ end
+
+ def test_recognize_head_request_as_get_route
+ path = Path::Pattern.new "/books(/:action(.:format))"
+ app = Object.new
+ conditions = {
+ :request_method => 'GET'
+ }
+ @router.routes.add_route(app, path, conditions, {})
+
+ env = rails_env 'PATH_INFO' => '/books/list.rss',
+ "REQUEST_METHOD" => "HEAD"
+
+ called = false
+ @router.recognize(env) do |r, _, params|
+ called = true
+ end
+
+ assert called
+ end
+
+ def test_recognize_cares_about_verbs
+ path = Path::Pattern.new "/books(/:action(.:format))"
+ app = Object.new
+ conditions = {
+ :request_method => 'GET'
+ }
+ @router.routes.add_route(app, path, conditions, {})
+
+ conditions = conditions.dup
+ conditions[:request_method] = 'POST'
+
+ post = @router.routes.add_route(app, path, conditions, {})
+
+ env = rails_env 'PATH_INFO' => '/books/list.rss',
+ "REQUEST_METHOD" => "POST"
+
+ called = false
+ @router.recognize(env) do |r, _, params|
+ assert_equal post, r
+ called = true
+ end
+
+ assert called
+ end
+
+ private
+
+ def add_routes router, paths
+ paths.each do |path|
+ path = Path::Pattern.new path
+ router.routes.add_route nil, path, {}, {}, {}
+ end
+ end
+
+ RailsEnv = Struct.new(:env)
+
+ def rails_env env
+ RailsEnv.new rack_env env
+ end
+
+ def rack_env env
+ {
+ "rack.version" => [1, 1],
+ "rack.input" => StringIO.new,
+ "rack.errors" => StringIO.new,
+ "rack.multithread" => true,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+ "REQUEST_METHOD" => "GET",
+ "SERVER_NAME" => "example.org",
+ "SERVER_PORT" => "80",
+ "QUERY_STRING" => "",
+ "PATH_INFO" => "/content",
+ "rack.url_scheme" => "http",
+ "HTTPS" => "off",
+ "SCRIPT_NAME" => "",
+ "CONTENT_LENGTH" => "0"
+ }.merge env
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
new file mode 100644
index 0000000000..25e0321d31
--- /dev/null
+++ b/actionpack/test/journey/routes_test.rb
@@ -0,0 +1,53 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class TestRoutes < ActiveSupport::TestCase
+ def test_clear
+ routes = Routes.new
+ exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
+ path = Path::Pattern.new exp
+ requirements = { :hello => /world/ }
+
+ routes.add_route nil, path, requirements, {:id => nil}, {}
+ assert_equal 1, routes.length
+
+ routes.clear
+ assert_equal 0, routes.length
+ end
+
+ def test_ast
+ routes = Routes.new
+ path = Path::Pattern.new '/hello'
+
+ routes.add_route nil, path, {}, {}, {}
+ ast = routes.ast
+ routes.add_route nil, path, {}, {}, {}
+ assert_not_equal ast, routes.ast
+ end
+
+ def test_simulator_changes
+ routes = Routes.new
+ path = Path::Pattern.new '/hello'
+
+ routes.add_route nil, path, {}, {}, {}
+ sim = routes.simulator
+ routes.add_route nil, path, {}, {}, {}
+ assert_not_equal sim, routes.simulator
+ end
+
+ def test_first_name_wins
+ #def add_route app, path, conditions, defaults, name = nil
+ routes = Routes.new
+
+ one = Path::Pattern.new '/hello'
+ two = Path::Pattern.new '/aaron'
+
+ routes.add_route nil, one, {}, {}, 'aaron'
+ routes.add_route nil, two, {}, {}, 'aaron'
+
+ assert_equal '/hello', routes.named_routes['aaron'].path.spec.to_s
+ end
+ end
+ end
+end
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index eb1a54a81f..82c9d383ac 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -358,6 +358,17 @@ class AssetTagHelperTest < ActionView::TestCase
assert javascript_include_tag("prototype").html_safe?
end
+ def test_javascript_include_tag_relative_protocol
+ @controller.config.asset_host = "assets.example.com"
+ assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag('prototype', protocol: :relative)
+ end
+
+ def test_javascript_include_tag_default_protocol
+ @controller.config.asset_host = "assets.example.com"
+ @controller.config.default_asset_host_protocol = :relative
+ assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag('prototype')
+ end
+
def test_stylesheet_path
StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -398,7 +409,18 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_stylesheet_link_tag_should_not_output_the_same_asset_twice
- assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam')
+ assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam')
+ end
+
+ def test_stylesheet_link_tag_with_relative_protocol
+ @controller.config.asset_host = "assets.example.com"
+ assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', protocol: :relative)
+ end
+
+ def test_stylesheet_link_tag_with_default_protocol
+ @controller.config.asset_host = "assets.example.com"
+ @controller.config.default_asset_host_protocol = :relative
+ assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington')
end
def test_image_path
diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb
index 89aae4ac56..63b5ac0fab 100644
--- a/actionpack/test/template/atom_feed_helper_test.rb
+++ b/actionpack/test/template/atom_feed_helper_test.rb
@@ -238,7 +238,7 @@ class AtomFeedTest < ActionController::TestCase
get :index, :id=>"provide_builder"
# because we pass in the non-default builder, the content generated by the
# helper should go 'nowhere'. Leaving the response body blank.
- assert_blank @response.body
+ assert @response.body.blank?
end
end
diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb
index f493c8201d..02b1fd87a8 100644
--- a/actionpack/test/template/digestor_test.rb
+++ b/actionpack/test/template/digestor_test.rb
@@ -46,6 +46,12 @@ class TemplateDigestorTest < ActionView::TestCase
end
end
+ def test_explicit_dependency_in_multiline_erb_tag
+ assert_digest_difference("messages/show") do
+ change_template("messages/_form")
+ end
+ end
+
def test_second_level_dependency
assert_digest_difference("messages/show") do
change_template("comments/_comments")
diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionpack/test/template/form_collections_helper_test.rb
index c73e80ed88..2131f81396 100644
--- a/actionpack/test/template/form_collections_helper_test.rb
+++ b/actionpack/test/template/form_collections_helper_test.rb
@@ -149,6 +149,12 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_select 'label[for=post_category_id_2]', 'Category 2'
end
+ test 'collection radio accepts checked item which has a value of false' do
+ with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, :checked => false
+ assert_no_select 'input[type=radio][value=true][checked=checked]'
+ assert_select 'input[type=radio][value=false][checked=checked]'
+ end
+
# COLLECTION CHECK BOXES
test 'collection check boxes accepts a collection and generate a serie of checkboxes for value method' do
collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index c730e3ab74..5e47e4db23 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -1171,7 +1171,6 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
-
def test_form_for_with_format
form_for(@post, :format => :json, :html => { :id => "edit_post_123", :class => "edit_post" }) do |f|
concat f.label(:title)
@@ -2693,6 +2692,19 @@ class FormHelperTest < ActionView::TestCase
end
end
+ def test_form_for_only_instantiates_builder_once
+ initialization_count = 0
+ builder_class = Class.new(ActionView::Helpers::FormBuilder) do
+ define_method :initialize do |*args|
+ super(*args)
+ initialization_count += 1
+ end
+ end
+
+ form_for(@post, builder: builder_class) { }
+ assert_equal 1, initialization_count, 'form builder instantiated more than once'
+ end
+
protected
def hidden_fields(method = nil)
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 54ab8b4d3a..c5efed3195 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -346,7 +346,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_optgroups_with_with_options_with_hash
assert_dom_equal(
- "<optgroup label=\"Europe\"><option value=\"Denmark\">Denmark</option>\n<option value=\"Germany\">Germany</option></optgroup><optgroup label=\"North America\"><option value=\"United States\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup>",
+ "<optgroup label=\"North America\"><option value=\"United States\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"Denmark\">Denmark</option>\n<option value=\"Germany\">Germany</option></optgroup>",
grouped_options_for_select({'North America' => ['United States','Canada'], 'Europe' => ['Denmark','Germany']})
)
end
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index a84034c02e..9c49438f6a 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -25,33 +25,33 @@ class RecordTagHelperTest < ActionView::TestCase
def test_content_tag_for
expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>)
- actual = content_tag_for(:li, @post) { }
+ actual = content_tag_for(:li, @post)
assert_dom_equal expected, actual
end
def test_content_tag_for_prefix
expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>)
- actual = content_tag_for(:ul, @post, :archived) { }
+ actual = content_tag_for(:ul, @post, :archived)
assert_dom_equal expected, actual
end
def test_content_tag_for_with_extra_html_options
expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, :class => "special", :style => "background-color: #f0f0f0") { }
+ actual = content_tag_for(:tr, @post, class: "special", style: "background-color: #f0f0f0")
assert_dom_equal expected, actual
end
def test_content_tag_for_with_prefix_and_extra_html_options
expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, :archived, :class => "special", :style => "background-color: #f0f0f0") { }
+ actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0")
assert_dom_equal expected, actual
end
def test_block_not_in_erb_multiple_calls
expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
- actual = div_for(@post, :class => "special") { @post.body }
+ actual = div_for(@post, class: "special") { @post.body }
assert_dom_equal expected, actual
- actual = div_for(@post, :class => "special") { @post.body }
+ actual = div_for(@post, class: "special") { @post.body }
assert_dom_equal expected, actual
end
@@ -63,7 +63,7 @@ class RecordTagHelperTest < ActionView::TestCase
def test_div_for_in_erb
expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
- actual = render_erb("<%= div_for(@post, :class => 'special') do %><%= @post.body %><% end %>")
+ actual = render_erb("<%= div_for(@post, class: 'special') do %><%= @post.body %><% end %>")
assert_dom_equal expected, actual
end
@@ -75,6 +75,14 @@ class RecordTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_content_tag_for_collection_without_given_block
+ post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!" }
+ post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!" }
+ expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>)
+ actual = content_tag_for(:li, [post_1, post_2])
+ assert_dom_equal expected, actual
+ end
+
def test_div_for_collection
post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
@@ -84,7 +92,7 @@ class RecordTagHelperTest < ActionView::TestCase
end
def test_content_tag_for_single_record_is_html_safe
- result = div_for(@post, :class => "special") { @post.body }
+ result = div_for(@post, class: "special") { @post.body }
assert result.html_safe?
end
@@ -96,8 +104,8 @@ class RecordTagHelperTest < ActionView::TestCase
end
def test_content_tag_for_does_not_change_options_hash
- options = { :class => "important" }
- content_tag_for(:li, @post, options) { }
- assert_equal({ :class => "important" }, options)
+ options = { class: "important" }
+ content_tag_for(:li, @post, options)
+ assert_equal({ class: "important" }, options)
end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 4e6a676fc6..9fb26e32b1 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -437,6 +437,11 @@ module RenderTestCases
@view.render(:partial => 'test/partial_with_layout_block_content', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'})
end
+ def test_render_partial_with_layout_raises_descriptive_error
+ e = assert_raises(ActionView::MissingTemplate) { @view.render(partial: 'test/partial', layout: true) }
+ assert_match "Missing partial /true with", e.message
+ end
+
def test_render_with_nested_layout
assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n),
@view.render(:file => "test/nested_layout", :layout => "layouts/yield")
diff --git a/actionpack/test/template/spec_type_test.rb b/actionpack/test/template/spec_type_test.rb
deleted file mode 100644
index 08a7bdf81d..0000000000
--- a/actionpack/test/template/spec_type_test.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'abstract_unit'
-
-class ActionViewSpecTypeTest < ActiveSupport::TestCase
- def assert_view actual
- assert_equal ActionView::TestCase, actual
- end
-
- def refute_view actual
- refute_equal ActionView::TestCase, actual
- end
-
- def test_spec_type_resolves_for_matching_helper_strings
- assert_view MiniTest::Spec.spec_type("WidgetHelper")
- assert_view MiniTest::Spec.spec_type("WidgetHelperTest")
- assert_view MiniTest::Spec.spec_type("Widget Helper Test")
- # And is not case sensitive
- assert_view MiniTest::Spec.spec_type("widgethelper")
- assert_view MiniTest::Spec.spec_type("widgethelpertest")
- assert_view MiniTest::Spec.spec_type("widget helper test")
- end
-
- def test_spec_type_resolves_for_matching_view_strings
- assert_view MiniTest::Spec.spec_type("WidgetView")
- assert_view MiniTest::Spec.spec_type("WidgetViewTest")
- assert_view MiniTest::Spec.spec_type("Widget View Test")
- # And is not case sensitive
- assert_view MiniTest::Spec.spec_type("widgetview")
- assert_view MiniTest::Spec.spec_type("widgetviewtest")
- assert_view MiniTest::Spec.spec_type("widget view test")
- end
-
- def test_spec_type_wont_match_non_space_characters
- refute_view MiniTest::Spec.spec_type("Widget Helper\tTest")
- refute_view MiniTest::Spec.spec_type("Widget Helper\rTest")
- refute_view MiniTest::Spec.spec_type("Widget Helper\nTest")
- refute_view MiniTest::Spec.spec_type("Widget Helper\fTest")
- refute_view MiniTest::Spec.spec_type("Widget HelperXTest")
- end
-end
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index ed9d303158..8d32205fb8 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -82,8 +82,8 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def test_text_template_does_not_html_escape
- @template = new_template("<%= apostrophe %>", format: :text)
- assert_equal "l'apostrophe", render
+ @template = new_template("<%= apostrophe %> <%== apostrophe %>", format: :text)
+ assert_equal "l'apostrophe l'apostrophe", render
end
def test_raw_template
diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb
index e843a1deb4..108a674d95 100644
--- a/actionpack/test/template/test_test.rb
+++ b/actionpack/test/template/test_test.rb
@@ -78,59 +78,3 @@ class CrazyStringHelperTest < ActionView::TestCase
assert_equal PeopleHelper, self.class.helper_class
end
end
-
-describe PeopleHelper do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
-end
-
-describe PeopleHelper, :helper_class do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
-end
-
-describe PeopleHelper do
- describe "even while nested" do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
- end
-end
-
-describe PeopleHelper, :helper_class do
- describe "even while nested" do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
- end
-end
-
-describe "PeopleHelper" do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
-end
-
-describe "PeopleHelperTest" do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
-end
-
-describe "PeopleHelper" do
- describe "even while nested" do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
- end
-end
-
-describe "PeopleHelperTest" do
- describe "even while nested" do
- it "resolves the right helper_class" do
- assert_equal PeopleHelper, self.class.helper_class
- end
- end
-end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 1bb625213d..ba65349b6a 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -517,16 +517,6 @@ class UrlHelperTest < ActiveSupport::TestCase
mail_to("david@loudthinking.com", "David Heinemeier Hansson", class: "admin")
end
- def test_mail_to_with_javascript
- snippet = mail_to("me@domain.com", "My email", encode: "javascript")
- assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
- end
-
- def test_mail_to_with_javascript_unicode
- snippet = mail_to("unicode@example.com", "únicode", encode: "javascript")
- assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
- end
-
def test_mail_with_options
assert_dom_equal(
%{<a href="mailto:me@example.com?cc=ccaddress%40example.com&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email">My email</a>},
@@ -539,54 +529,8 @@ class UrlHelperTest < ActiveSupport::TestCase
mail_to('feedback@example.com', '<img src="/feedback.png" />'.html_safe)
end
- def test_mail_to_with_hex
- assert_dom_equal(
- %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>},
- mail_to("me@domain.com", "My email", encode: "hex")
- )
-
- assert_dom_equal(
- %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">&#109;&#101;&#64;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>},
- mail_to("me@domain.com", nil, encode: "hex")
- )
- end
-
- def test_mail_to_with_replace_options
- assert_dom_equal(
- %{<a href="mailto:wolfgang@stufenlos.net">wolfgang(at)stufenlos(dot)net</a>},
- mail_to("wolfgang@stufenlos.net", nil, replace_at: "(at)", replace_dot: "(dot)")
- )
-
- assert_dom_equal(
- %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>},
- mail_to("me@domain.com", nil, encode: "hex", replace_at: "(at)")
- )
-
- assert_dom_equal(
- %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>},
- mail_to("me@domain.com", "My email", encode: "hex", replace_at: "(at)")
- )
-
- assert_dom_equal(
- %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>},
- mail_to("me@domain.com", nil, encode: "hex", replace_at: "(at)", replace_dot: "(dot)")
- )
-
- assert_dom_equal(
- %{<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>},
- mail_to("me@domain.com", "My email", encode: "javascript", replace_at: "(at)", replace_dot: "(dot)")
- )
-
- assert_dom_equal(
- %{<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>},
- mail_to("me@domain.com", nil, encode: "javascript", replace_at: "(at)", replace_dot: "(dot)")
- )
- end
-
def test_mail_to_returns_html_safe_string
assert mail_to("david@loudthinking.com").html_safe?
- assert mail_to("me@domain.com", "My email", encode: "javascript").html_safe?
- assert mail_to("me@domain.com", "My email", encode: "hex").html_safe?
end
def protect_against_forgery?
diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb
index c44c5d8968..55620abe84 100644
--- a/actionpack/test/ts_isolated.rb
+++ b/actionpack/test/ts_isolated.rb
@@ -1,4 +1,4 @@
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'rbconfig'
require 'abstract_unit'
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 133bb558a9..09e6ede064 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,6 +1,44 @@
## Rails 4.0.0 (unreleased) ##
-* Use BCrypt's MIN_COST in the test environment for speedier tests when using `has_secure_pasword`.
+* Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the
+ absence of attributes.
+
+ class Person
+ include ActiveModel::Validations
+
+ attr_accessor :first_name
+ validates_absence_of :first_name
+ end
+
+ person = Person.new
+ person.first_name = "John"
+ person.valid?
+ # => false
+ person.errors.messages
+ # => {:first_name=>["must be blank"]}
+
+ *Roberto Vasquez Angel*
+
+* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`
+
+ *Renato Mascarenhas*
+
+* Observers was extracted from Active Model as `rails-observers` gem.
+
+ *Rafael Mendonça França*
+
+* Specify type of singular association during serialization *Steve Klabnik*
+
+* Fixed length validator to correctly handle nil values. Fixes #7180.
+
+ *Michal Zima*
+
+* Removed dispensable `require` statements. Make sure to require `active_model` before requiring
+ individual parts of the framework.
+
+ *Yves Senn*
+
+* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_pasword`.
*Brian Cardarella + Jeremy Kemper + Trevor Turk*
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index 810daf856c..5c668d9624 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2012 David Heinemeier Hansson
+Copyright (c) 2004-2013 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb
index 0b2706076f..a56ec4db39 100644
--- a/activemodel/examples/validations.rb
+++ b/activemodel/examples/validations.rb
@@ -22,8 +22,8 @@ class Person
end
person1 = Person.new
-p person1.valid?
-person1.errors
+p person1.valid? # => false
+p person1.errors.messages # => {:name=>["can't be blank"]}
person2 = Person.new(:name => "matz")
-p person2.valid?
+p person2.valid? # => true
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index f757ba9843..3bd5531356 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -40,8 +40,6 @@ module ActiveModel
autoload :DeprecatedMassAssignmentSecurity
autoload :Name, 'active_model/naming'
autoload :Naming
- autoload :Observer, 'active_model/observing'
- autoload :Observing
autoload :SecurePassword
autoload :Serialization
autoload :TestCase
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 86eaad830e..db5759ada9 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,3 +1,4 @@
+require 'thread_safe'
module ActiveModel
# Raised when an attribute is not defined.
@@ -8,7 +9,7 @@ module ActiveModel
#
# user = User.first
# user.pets.select(:id).first.user_id
- # # => ActiveModel::MissingAttributeError: missing attribute: user_id
+ # # => ActiveModel::MissingAttributeError: missing attribute: user_id
class MissingAttributeError < NoMethodError
end
# == Active \Model Attribute Methods
@@ -202,7 +203,7 @@ module ActiveModel
# person.name # => "Bob"
# person.nickname # => "Bob"
# person.name_short? # => true
- # person.nickname_short? # => true
+ # person.nickname_short? # => true
def alias_attribute(new_name, old_name)
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
attribute_method_matchers.each do |matcher|
@@ -337,17 +338,17 @@ module ActiveModel
# significantly (in our case our test suite finishes 10% faster with
# this cache).
def attribute_method_matchers_cache #:nodoc:
- @attribute_method_matchers_cache ||= {}
+ @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(:initial_capacity => 4)
end
def attribute_method_matcher(method_name) #:nodoc:
- attribute_method_matchers_cache.fetch(method_name) do |name|
+ attribute_method_matchers_cache.compute_if_absent(method_name) do
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
# will match every time.
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
match = nil
- matchers.detect { |method| match = method.match(name) }
- attribute_method_matchers_cache[name] = match
+ matchers.detect { |method| match = method.match(method_name) }
+ match
end
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index eab94554cc..c52e4947ae 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -1,5 +1,3 @@
-require 'active_support/callbacks'
-
module ActiveModel
# == Active \Model \Callbacks
#
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 42de32e63e..1f5d23dd8e 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -1,5 +1,3 @@
-require 'active_support/inflector'
-
module ActiveModel
# == Active \Model Conversions
#
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 9d09353ef2..ecb7a9e9b1 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,4 +1,3 @@
-require 'active_model/attribute_methods'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
@@ -175,7 +174,10 @@ module ActiveModel
# Handle <tt>reset_*!</tt> for +method_missing+.
def reset_attribute!(attr)
- __send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
+ if attribute_changed?(attr)
+ __send__("#{attr}=", changed_attributes[attr])
+ changed_attributes.delete(attr)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index c82d4f012c..963e52bff3 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -122,7 +122,7 @@ module ActiveModel
# Delete messages for +key+. Returns the deleted messages.
#
# person.errors.get(:name) # => ["can not be nil"]
- # person.errors.delete(:name) # => ["can not be nil"]
+ # person.errors.delete(:name) # => ["can not be nil"]
# person.errors.get(:name) # => nil
def delete(key)
messages.delete(key)
@@ -213,7 +213,7 @@ module ActiveModel
# Returns +true+ if no errors are found, +false+ otherwise.
# If the error message is a string it can be empty.
#
- # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.full_messages # => ["name can not be nil"]
# person.errors.empty? # => false
def empty?
all? { |k, v| v && v.empty? && !v.is_a?(String) }
@@ -246,7 +246,7 @@ module ActiveModel
to_hash(options && options[:full_messages])
end
- # Returns a Hash of attributes with their error messages. If +full_messages+
+ # Returns a Hash of attributes with their error messages. If +full_messages+
# is +true+, it will contain full messages (see +full_message+).
#
# person.to_hash # => {:name=>["can not be nil"]}
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index d17848c861..540e8132d3 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -13,6 +13,7 @@ en:
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
+ present: "must be blank"
too_long: "is too long (maximum is %{count} characters)"
too_short: "is too short (minimum is %{count} characters)"
wrong_length: "is the wrong length (should be %{count} characters)"
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 264880eecd..6887f6d781 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,4 +1,3 @@
-require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
@@ -56,8 +55,8 @@ module ActiveModel
# end
#
# BlogPost.model_name <=> 'BlogPost' # => 0
- # BlogPost.model_name <=> 'Blog' # => 1
- # BlogPost.model_name <=> 'BlogPosts' # => -1
+ # BlogPost.model_name <=> 'Blog' # => 1
+ # BlogPost.model_name <=> 'BlogPosts' # => -1
##
# :method: =~
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
deleted file mode 100644
index 77bc0f71e3..0000000000
--- a/activemodel/lib/active_model/observer_array.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-require 'set'
-
-module ActiveModel
- # Stores the enabled/disabled state of individual observers for
- # a particular model class.
- class ObserverArray < Array
- attr_reader :model_class
- def initialize(model_class, *args) #:nodoc:
- @model_class = model_class
- super(*args)
- end
-
- # Returns +true+ if the given observer is disabled for the model class,
- # +false+ otherwise.
- def disabled_for?(observer) #:nodoc:
- disabled_observers.include?(observer.class)
- end
-
- # 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
- #
- # User.observers.disable AuditTrail
- # # => disables the AuditTrail observer for User notifications.
- # # Other models will still notify the AuditTrail observer.
- #
- # ORM.observers.disable :observer_1, :observer_2
- # # => disables Observer1 and Observer2 for all models.
- #
- # User.observers.disable :all do
- # # all user observers are disabled for
- # # just the duration of the block
- # end
- def disable(*observers, &block)
- set_enablement(false, observers, &block)
- end
-
- # 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
- #
- # User.observers.enable AuditTrail
- # # => enables the AuditTrail observer for User notifications.
- # # Other models will not be affected (i.e. they will not
- # # trigger notifications to AuditTrail if previously disabled)
- #
- # ORM.observers.enable :observer_1, :observer_2
- # # => enables Observer1 and Observer2 for all models.
- #
- # User.observers.enable :all do
- # # all user observers are enabled for
- # # just the duration of the block
- # end
- #
- # Note: all observers are enabled by default. This method is only
- # useful when you have previously disabled one or more observers.
- def enable(*observers, &block)
- set_enablement(true, observers, &block)
- end
-
- protected
-
- def disabled_observers #:nodoc:
- @disabled_observers ||= Set.new
- end
-
- def observer_class_for(observer) #:nodoc:
- return observer if observer.is_a?(Class)
-
- if observer.respond_to?(:to_sym) # string/symbol
- observer.to_s.camelize.constantize
- else
- raise ArgumentError, "#{observer} was not a class or a " +
- "lowercase, underscored class name as expected."
- end
- end
-
- def start_transaction #:nodoc:
- disabled_observer_stack.push(disabled_observers.dup)
- each_subclass_array do |array|
- array.start_transaction
- end
- end
-
- def disabled_observer_stack #:nodoc:
- @disabled_observer_stack ||= []
- end
-
- def end_transaction #:nodoc:
- @disabled_observers = disabled_observer_stack.pop
- each_subclass_array do |array|
- array.end_transaction
- end
- end
-
- def transaction #:nodoc:
- start_transaction
-
- begin
- yield
- ensure
- end_transaction
- end
- end
-
- def each_subclass_array #:nodoc:
- model_class.descendants.each do |subclass|
- yield subclass.observers
- end
- end
-
- def set_enablement(enabled, observers) #:nodoc:
- if block_given?
- transaction do
- set_enablement(enabled, observers)
- yield
- end
- else
- observers = ActiveModel::Observer.descendants if observers == [:all]
- observers.each do |obs|
- klass = observer_class_for(obs)
-
- unless klass < ActiveModel::Observer
- raise ArgumentError.new("#{obs} does not refer to a valid observer")
- end
-
- if enabled
- disabled_observers.delete(klass)
- else
- disabled_observers << klass
- end
- end
-
- each_subclass_array do |array|
- array.set_enablement(enabled, observers)
- end
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
deleted file mode 100644
index 5f1c99ce62..0000000000
--- a/activemodel/lib/active_model/observing.rb
+++ /dev/null
@@ -1,374 +0,0 @@
-require 'singleton'
-require 'active_model/observer_array'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/enumerable'
-require 'active_support/core_ext/object/try'
-require 'active_support/descendants_tracker'
-
-module ActiveModel
- # == Active \Model Observers Activation
- module Observing
- extend ActiveSupport::Concern
-
- included do
- extend ActiveSupport::DescendantsTracker
- end
-
- module ClassMethods
- # Activates the observers assigned.
- #
- # class ORM
- # include ActiveModel::Observing
- # end
- #
- # # Calls PersonObserver.instance
- # ORM.observers = :person_observer
- #
- # # Calls Cacher.instance and GarbageCollector.instance
- # ORM.observers = :cacher, :garbage_collector
- #
- # # Same as above, just using explicit class references
- # ORM.observers = Cacher, GarbageCollector
- #
- # Note: Setting this does not instantiate the observers yet.
- # <tt>instantiate_observers</tt> is called during startup, and before
- # each development request.
- def observers=(*values)
- observers.replace(values.flatten)
- end
-
- # Gets an array of observers observing this model. The array also provides
- # +enable+ and +disable+ methods that allow you to selectively enable and
- # disable observers (see ActiveModel::ObserverArray.enable and
- # ActiveModel::ObserverArray.disable for more on this).
- #
- # class ORM
- # include ActiveModel::Observing
- # end
- #
- # ORM.observers = :cacher, :garbage_collector
- # ORM.observers # => [:cacher, :garbage_collector]
- # ORM.observers.class # => ActiveModel::ObserverArray
- def observers
- @observers ||= ObserverArray.new(self)
- end
-
- # Returns the current observer instances.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- # Foo.instantiate_observers
- #
- # Foo.observer_instances # => [#<FooObserver:0x007fc212c40820>]
- def observer_instances
- @observer_instances ||= []
- end
-
- # Instantiate the global observers.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- #
- # foo = Foo.new
- # foo.status = false
- # foo.notify_observers(:on_spec)
- # foo.status # => false
- #
- # Foo.instantiate_observers # => [FooObserver]
- #
- # foo = Foo.new
- # foo.status = false
- # foo.notify_observers(:on_spec)
- # foo.status # => true
- def instantiate_observers
- observers.each { |o| instantiate_observer(o) }
- end
-
- # Add a new observer to the pool. The new observer needs to respond to
- # <tt>update</tt>, otherwise it raises an +ArgumentError+ exception.
- #
- # class Foo
- # include ActiveModel::Observing
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # end
- #
- # Foo.add_observer(FooObserver.instance)
- #
- # Foo.observers_instance
- # # => [#<FooObserver:0x007fccf55d9390>]
- def add_observer(observer)
- unless observer.respond_to? :update
- raise ArgumentError, "observer needs to respond to 'update'"
- end
- observer_instances << observer
- end
-
- # Fires notifications to model's observers.
- #
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
- # end
- #
- # Custom notifications can be sent in a similar fashion:
- #
- # notify_observers(:custom_notification, :foo)
- #
- # This will call <tt>custom_notification</tt>, passing as arguments
- # the current object and <tt>:foo</tt>.
- def notify_observers(*args)
- observer_instances.each { |observer| observer.update(*args) }
- end
-
- # Returns the total number of instantiated observers.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- # Foo.observers_count # => 0
- # Foo.instantiate_observers
- # Foo.observers_count # => 1
- def observers_count
- observer_instances.size
- end
-
- # <tt>count_observers</tt> is deprecated. Use #observers_count.
- def count_observers
- msg = "count_observers is deprecated in favor of observers_count"
- ActiveSupport::Deprecation.warn msg
- observers_count
- end
-
- protected
- def instantiate_observer(observer) #:nodoc:
- # string/symbol
- if observer.respond_to?(:to_sym)
- observer = observer.to_s.camelize.constantize
- end
- if observer.respond_to?(:instance)
- observer.instance
- else
- raise ArgumentError,
- "#{observer} must be a lowercase, underscored class name (or " +
- "the class itself) responding to the method :instance. " +
- "Example: Person.observers = :big_brother # calls " +
- "BigBrother.instance"
- end
- end
-
- # Notify observers when the observed class is subclassed.
- def inherited(subclass) #:nodoc:
- super
- notify_observers :observed_class_inherited, subclass
- end
- end
-
- # Notify a change to the list of observers.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- # Foo.instantiate_observers # => [FooObserver]
- #
- # foo = Foo.new
- # foo.status = false
- # foo.notify_observers(:on_spec)
- # foo.status # => true
- #
- # See ActiveModel::Observing::ClassMethods.notify_observers for more
- # information.
- def notify_observers(method, *extra_args)
- self.class.notify_observers(method, self, *extra_args)
- end
- end
-
- # == Active \Model Observers
- #
- # Observer classes respond to life cycle callbacks to implement trigger-like
- # behavior outside the original class. This is a great way to reduce the
- # clutter that normally comes when the model class is burdened with
- # functionality that doesn't pertain to the core responsibility of the
- # class.
- #
- # class CommentObserver < ActiveModel::Observer
- # def after_save(comment)
- # Notifications.comment('admin@do.com', 'New comment was posted', comment).deliver
- # end
- # end
- #
- # This Observer sends an email when a <tt>Comment#save</tt> is finished.
- #
- # class ContactObserver < ActiveModel::Observer
- # def after_create(contact)
- # contact.logger.info('New contact added!')
- # end
- #
- # def after_destroy(contact)
- # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
- # end
- # end
- #
- # This Observer uses logger to log when specific callbacks are triggered.
- #
- # == \Observing a class that can't be inferred
- #
- # Observers will by default be mapped to the class with which they share a
- # name. So <tt>CommentObserver</tt> will be tied to observing <tt>Comment</tt>,
- # <tt>ProductManagerObserver</tt> to <tt>ProductManager</tt>, and so on. If
- # you want to name your observer differently than the class you're interested
- # in observing, you can use the <tt>Observer.observe</tt> class method which
- # takes either the concrete class (<tt>Product</tt>) or a symbol for that
- # class (<tt>:product</tt>):
- #
- # class AuditObserver < ActiveModel::Observer
- # observe :account
- #
- # def after_update(account)
- # AuditTrail.new(account, 'UPDATED')
- # end
- # end
- #
- # If the audit observer needs to watch more than one kind of object, this can
- # be specified with multiple arguments:
- #
- # class AuditObserver < ActiveModel::Observer
- # observe :account, :balance
- #
- # def after_update(record)
- # AuditTrail.new(record, 'UPDATED')
- # end
- # end
- #
- # The <tt>AuditObserver</tt> will now act on both updates to <tt>Account</tt>
- # and <tt>Balance</tt> by treating them both as records.
- #
- # If you're using an Observer in a Rails application with Active Record, be
- # sure to read about the necessary configuration in the documentation for
- # ActiveRecord::Observer.
- class Observer
- include Singleton
- extend ActiveSupport::DescendantsTracker
-
- class << self
- # Attaches the observer to the supplied model classes.
- #
- # class AuditObserver < ActiveModel::Observer
- # observe :account, :balance
- # end
- #
- # AuditObserver.observed_classes # => [Account, Balance]
- def observe(*models)
- models.flatten!
- models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- singleton_class.redefine_method(:observed_classes) { models }
- end
-
- # Returns an array of Classes to observe.
- #
- # AccountObserver.observed_classes # => [Account]
- #
- # You can override this instead of using the +observe+ helper.
- #
- # class AuditObserver < ActiveModel::Observer
- # def self.observed_classes
- # [Account, Balance]
- # end
- # end
- def observed_classes
- Array(observed_class)
- end
-
- # Returns the class observed by default. It's inferred from the observer's
- # class name.
- #
- # PersonObserver.observed_class # => Person
- # AccountObserver.observed_class # => Account
- def observed_class
- name[/(.*)Observer/, 1].try :constantize
- end
- end
-
- # Start observing the declared classes and their subclasses.
- # Called automatically by the instance method.
- def initialize #:nodoc:
- observed_classes.each { |klass| add_observer!(klass) }
- end
-
- def observed_classes #:nodoc:
- self.class.observed_classes
- end
-
- # Send observed_method(object) if the method exists and
- # the observer is enabled for the given object's class.
- def update(observed_method, object, *extra_args, &block) #:nodoc:
- return if !respond_to?(observed_method) || disabled_for?(object)
- send(observed_method, object, *extra_args, &block)
- end
-
- # Special method sent by the observed class when it is inherited.
- # Passes the new subclass.
- def observed_class_inherited(subclass) #:nodoc:
- self.class.observe(observed_classes + [subclass])
- add_observer!(subclass)
- end
-
- protected
- def add_observer!(klass) #:nodoc:
- klass.add_observer(self)
- end
-
- # Returns true if notifications are disabled for this object.
- def disabled_for?(object) #:nodoc:
- klass = object.class
- return false unless klass.respond_to?(:observers)
- klass.observers.disabled_for?(self)
- end
- end
-end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index dfd68a90fd..fdb06aebb9 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -90,7 +90,7 @@ module ActiveModel
# person.name = 'bob'
# person.age = 22
# person.serializable_hash # => {"name"=>"bob", "age"=>22}
- # person.serializable_hash(only: :name) # => {"name"=>"bob"}
+ # person.serializable_hash(only: :name) # => {"name"=>"bob"}
# person.serializable_hash(except: :name) # => {"age"=>22}
# person.serializable_hash(methods: :capitalized_name)
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index fb6093cce5..648ae7ce3d 100755..100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/time/acts_like'
module ActiveModel
module Serializers
@@ -20,7 +21,11 @@ module ActiveModel
def initialize(name, serializable, value)
@name, @serializable = name, serializable
- value = value.in_time_zone if value.respond_to?(:in_time_zone)
+
+ if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
+ value = value.in_time_zone
+ end
+
@value = value
@type = compute_type
end
@@ -149,7 +154,12 @@ module ActiveModel
end
else
merged_options[:root] = association.to_s
- records.to_xml(merged_options)
+
+ unless records.class.to_s.underscore == association.to_s
+ merged_options[:type] = records.class.name
+ end
+
+ records.to_xml merged_options
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 2524b8d065..2db4a25f61 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,9 +1,6 @@
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/except'
-require 'active_model/errors'
-require 'active_model/validations/callbacks'
-require 'active_model/validator'
module ActiveModel
diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb
new file mode 100644
index 0000000000..1a1863370b
--- /dev/null
+++ b/activemodel/lib/active_model/validations/absence.rb
@@ -0,0 +1,31 @@
+module ActiveModel
+ module Validations
+ # == Active Model Absence Validator
+ class AbsenceValidator < EachValidator #:nodoc:
+ def validate_each(record, attr_name, value)
+ record.errors.add(attr_name, :present, options) if value.present?
+ end
+ end
+
+ module HelperMethods
+ # Validates that the specified attributes are blank (as defined by
+ # Object#blank?). Happens by default on save.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_absence_of :first_name
+ # end
+ #
+ # The first_name attribute must be in the object and it must be blank.
+ #
+ # Configuration options:
+ # * <tt>:message</tt> - A custom error message (default is: "must be blank").
+ #
+ # There is also a list of default options supported by every validator:
+ # +:if+, +:unless+, +:on+ and +:strict+.
+ # See <tt>ActiveModel::Validation#validates</tt> for more information
+ def validates_absence_of(*attr_names)
+ validates_with AbsenceValidator, _merge_attributes(attr_names)
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index a8fb4fdfc2..e28ad2841b 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -1,5 +1,3 @@
-require 'active_support/callbacks'
-
module ActiveModel
module Validations
# == Active \Model Validation Callbacks
@@ -85,8 +83,8 @@ module ActiveModel
# person = Person.new
# person.name = ''
# person.valid? # => false
- # person.status # => false
- #  person.name = 'bob'
+ # person.status # => false
+ # person.name = 'bob'
# person.valid? # => true
# person.status # => true
def after_validation(*args, &block)
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 70ef589cd7..675fb5f1e5 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -14,6 +14,10 @@ module ActiveModel
options[:minimum], options[:maximum] = range.min, range.max
end
+ if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
+ options[:minimum] = 1
+ end
+
super
end
@@ -40,7 +44,10 @@ module ActiveModel
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
- next if value_length.send(validity_check, check_value)
+
+ if !value.nil? || skip_nil_check?(key)
+ next if value_length.send(validity_check, check_value)
+ end
errors_options[:count] = check_value
@@ -58,6 +65,10 @@ module ActiveModel
options[:tokenizer].call(value)
end || value
end
+
+ def skip_nil_check?(key)
+ key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
+ end
end
module HelperMethods
@@ -79,7 +90,8 @@ module ActiveModel
#
# Configuration options:
# * <tt>:minimum</tt> - The minimum size of the attribute.
- # * <tt>:maximum</tt> - The maximum size of the attribute.
+ # * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
+ # default if not used with :minimum.
# * <tt>:is</tt> - The exact size of the attribute.
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
# the attribute.
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index ae84c376b9..ab8c8359fc 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -3,8 +3,8 @@ module ActiveModel
module Validations
class PresenceValidator < EachValidator # :nodoc:
- def validate(record)
- record.errors.add_on_blank(attributes, options)
+ def validate_each(record, attr_name, value)
+ record.errors.add(attr_name, :blank, options) if value.blank?
end
end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 629b157fed..d51f4d1936 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -109,8 +109,8 @@ module ActiveModel
# Return the kind for this validator.
#
- # PresenceValidator.new.kind # => :presence
- # UniquenessValidator.new.kind # => :uniqueness 
+ # PresenceValidator.new.kind # => :presence
+ # UniquenessValidator.new.kind # => :uniqueness
def kind
self.class.kind
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index eaaf910bac..0b9f9537e2 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -78,7 +78,7 @@ class DirtyTest < ActiveModel::TestCase
@model.name = "Bob"
@model.reset_name!
assert_nil @model.name
- #assert !@model.name_changed #Doesn't work yet
+ assert !@model.name_changed?
end
test "setting color to same value should not result in change being recorded" do
@@ -114,5 +114,4 @@ class DirtyTest < ActiveModel::TestCase
assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change
assert_equal @model.name_was, "Otto"
end
-
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 293ce07f4e..1ffce1ae47 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -116,7 +116,7 @@ class ErrorsTest < ActiveModel::TestCase
test "added? should default message to :invalid" do
person = Person.new
- person.errors.add(:name, :invalid)
+ person.errors.add(:name)
assert person.errors.added?(:name)
end
@@ -161,7 +161,7 @@ class ErrorsTest < ActiveModel::TestCase
person = Person.new
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
- assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
+ assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages
end
test 'full_message should return the given message if attribute equals :base' do
@@ -240,4 +240,3 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add_on_blank :name, :message => 'custom'
end
end
-
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 7d6f11b5a5..7a63674757 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -7,4 +7,4 @@ require 'active_support/core_ext/string/access'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb
deleted file mode 100644
index fc5f18008b..0000000000
--- a/activemodel/test/cases/observer_array_test.rb
+++ /dev/null
@@ -1,220 +0,0 @@
-require 'cases/helper'
-require 'models/observers'
-
-class ObserverArrayTest < ActiveModel::TestCase
- def teardown
- ORM.observers.enable :all
- Budget.observers.enable :all
- Widget.observers.enable :all
- end
-
- def assert_observer_notified(model_class, observer_class)
- observer_class.instance.before_save_invocations.clear
- model_instance = model_class.new
- model_instance.save
- assert_equal [model_instance], observer_class.instance.before_save_invocations
- end
-
- def assert_observer_not_notified(model_class, observer_class)
- observer_class.instance.before_save_invocations.clear
- model_instance = model_class.new
- model_instance.save
- assert_equal [], observer_class.instance.before_save_invocations
- end
-
- test "all observers are enabled by default" do
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can disable individual observers using a class constant" do
- ORM.observers.disable WidgetObserver
-
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can enable individual observers using a class constant" do
- ORM.observers.disable :all
- ORM.observers.enable AuditTrail
-
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_not_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can disable individual observers using a symbol" do
- ORM.observers.disable :budget_observer
-
- assert_observer_notified Widget, WidgetObserver
- assert_observer_not_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can enable individual observers using a symbol" do
- ORM.observers.disable :all
- ORM.observers.enable :audit_trail
-
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_not_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can disable multiple observers at a time" do
- ORM.observers.disable :widget_observer, :budget_observer
-
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_not_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can enable multiple observers at a time" do
- ORM.observers.disable :all
- ORM.observers.enable :widget_observer, :budget_observer
-
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_not_notified Widget, AuditTrail
- assert_observer_not_notified Budget, AuditTrail
- end
-
- test "can disable all observers using :all" do
- ORM.observers.disable :all
-
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_not_notified Budget, BudgetObserver
- assert_observer_not_notified Widget, AuditTrail
- assert_observer_not_notified Budget, AuditTrail
- end
-
- test "can enable all observers using :all" do
- ORM.observers.disable :all
- ORM.observers.enable :all
-
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can disable observers on individual models without affecting those observers on other models" do
- Widget.observers.disable :all
-
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_not_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can enable observers on individual models without affecting those observers on other models" do
- ORM.observers.disable :all
- Budget.observers.enable AuditTrail
-
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_not_notified Budget, BudgetObserver
- assert_observer_not_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can disable observers for the duration of a block" do
- yielded = false
- ORM.observers.disable :budget_observer do
- yielded = true
- assert_observer_notified Widget, WidgetObserver
- assert_observer_not_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- assert yielded
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "can enable observers for the duration of a block" do
- yielded = false
- Widget.observers.disable :all
-
- Widget.observers.enable :all do
- yielded = true
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- assert yielded
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_not_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "raises an appropriate error when a developer accidentally enables or disables the wrong class (i.e. Widget instead of WidgetObserver)" do
- assert_raise ArgumentError do
- ORM.observers.enable :widget
- end
-
- assert_raise ArgumentError do
- ORM.observers.enable Widget
- end
-
- assert_raise ArgumentError do
- ORM.observers.disable :widget
- end
-
- assert_raise ArgumentError do
- ORM.observers.disable Widget
- end
- end
-
- test "allows #enable at the superclass level to override #disable at the subclass level when called last" do
- Widget.observers.disable :all
- ORM.observers.enable :all
-
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- test "allows #disable at the superclass level to override #enable at the subclass level when called last" do
- Budget.observers.enable :audit_trail
- ORM.observers.disable :audit_trail
-
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_not_notified Widget, AuditTrail
- assert_observer_not_notified Budget, AuditTrail
- end
-
- test "can use the block form at different levels of the hierarchy" do
- yielded = false
- Widget.observers.disable :all
-
- ORM.observers.enable :all do
- yielded = true
- assert_observer_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-
- assert yielded
- assert_observer_not_notified Widget, WidgetObserver
- assert_observer_notified Budget, BudgetObserver
- assert_observer_not_notified Widget, AuditTrail
- assert_observer_notified Budget, AuditTrail
- end
-end
-
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
deleted file mode 100644
index ade6026602..0000000000
--- a/activemodel/test/cases/observing_test.rb
+++ /dev/null
@@ -1,181 +0,0 @@
-require 'cases/helper'
-
-class ObservedModel
- include ActiveModel::Observing
-
- class Observer
- end
-end
-
-class FooObserver < ActiveModel::Observer
- class << self
- public :new
- end
-
- attr_accessor :stub
-
- def on_spec(record, *args)
- stub.event_with(record, *args) if stub
- end
-
- def around_save(record)
- yield :in_around_save
- end
-end
-
-class Foo
- include ActiveModel::Observing
-end
-
-class ObservingTest < ActiveModel::TestCase
- def setup
- ObservedModel.observers.clear
- end
-
- test "initializes model with no cached observers" do
- assert ObservedModel.observers.empty?, "Not empty: #{ObservedModel.observers.inspect}"
- end
-
- test "stores cached observers in an array" do
- ObservedModel.observers << :foo
- assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
- end
-
- test "flattens array of assigned cached observers" do
- ObservedModel.observers = [[:foo], :bar]
- assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
- assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}"
- end
-
- test "uses an ObserverArray so observers can be disabled" do
- ObservedModel.observers = [:foo, :bar]
- assert ObservedModel.observers.is_a?(ActiveModel::ObserverArray)
- end
-
- test "instantiates observer names passed as strings" do
- ObservedModel.observers << 'foo_observer'
- FooObserver.expects(:instance)
- ObservedModel.instantiate_observers
- end
-
- test "instantiates observer names passed as symbols" do
- ObservedModel.observers << :foo_observer
- FooObserver.expects(:instance)
- ObservedModel.instantiate_observers
- end
-
- test "instantiates observer classes" do
- ObservedModel.observers << ObservedModel::Observer
- ObservedModel::Observer.expects(:instance)
- ObservedModel.instantiate_observers
- end
-
- test "raises an appropriate error when a developer accidentally adds the wrong class (i.e. Widget instead of WidgetObserver)" do
- assert_raise ArgumentError do
- ObservedModel.observers = ['string']
- ObservedModel.instantiate_observers
- end
- assert_raise ArgumentError do
- ObservedModel.observers = [:string]
- ObservedModel.instantiate_observers
- end
- assert_raise ArgumentError do
- ObservedModel.observers = [String]
- ObservedModel.instantiate_observers
- end
- end
-
- test "passes observers to subclasses" do
- FooObserver.instance
- bar = Class.new(Foo)
- assert_equal Foo.observers_count, bar.observers_count
- end
-end
-
-class ObserverTest < ActiveModel::TestCase
- def setup
- ObservedModel.observers = :foo_observer
- FooObserver.singleton_class.instance_eval do
- alias_method :original_observed_classes, :observed_classes
- end
- end
-
- def teardown
- FooObserver.singleton_class.instance_eval do
- undef_method :observed_classes
- alias_method :observed_classes, :original_observed_classes
- end
- end
-
- test "guesses implicit observable model name" do
- assert_equal Foo, FooObserver.observed_class
- end
-
- test "tracks implicit observable models" do
- instance = FooObserver.new
- assert_equal [Foo], instance.observed_classes
- end
-
- test "tracks explicit observed model class" do
- FooObserver.observe ObservedModel
- instance = FooObserver.new
- assert_equal [ObservedModel], instance.observed_classes
- end
-
- test "tracks explicit observed model as string" do
- FooObserver.observe 'observed_model'
- instance = FooObserver.new
- assert_equal [ObservedModel], instance.observed_classes
- end
-
- test "tracks explicit observed model as symbol" do
- FooObserver.observe :observed_model
- instance = FooObserver.new
- assert_equal [ObservedModel], instance.observed_classes
- end
-
- test "calls existing observer event" do
- foo = Foo.new
- FooObserver.instance.stub = stub
- FooObserver.instance.stub.expects(:event_with).with(foo)
- Foo.notify_observers(:on_spec, foo)
- end
-
- test "calls existing observer event from the instance" do
- foo = Foo.new
- FooObserver.instance.stub = stub
- FooObserver.instance.stub.expects(:event_with).with(foo)
- foo.notify_observers(:on_spec)
- end
-
- test "passes extra arguments" do
- foo = Foo.new
- FooObserver.instance.stub = stub
- FooObserver.instance.stub.expects(:event_with).with(foo, :bar)
- Foo.send(:notify_observers, :on_spec, foo, :bar)
- end
-
- test "skips nonexistent observer event" do
- foo = Foo.new
- Foo.notify_observers(:whatever, foo)
- end
-
- test "update passes a block on to the observer" do
- yielded_value = nil
- FooObserver.instance.update(:around_save, Foo.new) do |val|
- yielded_value = val
- end
- assert_equal :in_around_save, yielded_value
- end
-
- test "observe redefines observed_classes class method" do
- class BarObserver < ActiveModel::Observer
- observe :foo
- end
-
- assert_equal [Foo], BarObserver.observed_classes
-
- BarObserver.observe(ObservedModel)
- assert_equal [ObservedModel], BarObserver.observed_classes
- end
-end
diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb
index f89a288f8f..a0cd1402b1 100644
--- a/activemodel/test/cases/railtie_test.rb
+++ b/activemodel/test/cases/railtie_test.rb
@@ -5,10 +5,11 @@ class RailtieTest < ActiveModel::TestCase
include ActiveSupport::Testing::Isolation
def setup
- require 'rails/all'
+ require 'active_model/railtie'
- @app ||= Class.new(::Rails::Application).tap do |app|
- app.config.eager_load = false
+ @app ||= Class.new(::Rails::Application) do
+ config.eager_load = false
+ config.logger = Logger.new(STDOUT)
end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index c7e93370ec..7783bb25d5 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -5,13 +5,18 @@ require 'models/visitor'
require 'models/administrator'
class SecurePasswordTest < ActiveModel::TestCase
-
setup do
+ ActiveModel::SecurePassword.min_cost = true
+
@user = User.new
@visitor = Visitor.new
@oauthed_user = OauthedUser.new
end
+ teardown do
+ ActiveModel::SecurePassword.min_cost = false
+ end
+
test "blank password" do
@user.password = @visitor.password = ''
assert !@user.valid?(:create), 'user should be invalid'
@@ -70,13 +75,16 @@ class SecurePasswordTest < ActiveModel::TestCase
end
end
- test "Password digest cost defaults to bcrypt default cost" do
+ test "Password digest cost defaults to bcrypt default cost when min_cost is false" do
+ ActiveModel::SecurePassword.min_cost = false
+
@user.password = "secret"
assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost
end
test "Password digest cost can be set to bcrypt min cost to speed up tests" do
ActiveModel::SecurePassword.min_cost = true
+
@user.password = "secret"
assert_equal BCrypt::Engine::MIN_COST, @user.password_digest.cost
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index fd4d068354..9134c4980c 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -157,11 +157,8 @@ class JsonSerializationTest < ActiveModel::TestCase
test "as_json should keep the default order in the hash" do
json = @contact.as_json
- keys = json.keys
- %w(name age created_at awesome preferences).each_with_index do |field, index|
- assert_equal keys.index(field), index
- end
+ assert_equal %w(name age created_at awesome preferences), json.keys
end
test "from_json should work without a root (class attribute)" do
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index 90ddf8ff0c..99a9c1fe33 100755..100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -6,12 +6,12 @@ require 'ostruct'
class Contact
include ActiveModel::Serializers::Xml
- attr_accessor :address, :friends
+ attr_accessor :address, :friends, :contact
remove_method :attributes if method_defined?(:attributes)
def attributes
- instance_values.except("address", "friends")
+ instance_values.except("address", "friends", "contact")
end
end
@@ -56,6 +56,9 @@ class XmlSerializationTest < ActiveModel::TestCase
@contact.address.zip = 11111
@contact.address.apt_number = 35
@contact.friends = [Contact.new, Contact.new]
+ @related_contact = SerializableContact.new
+ @related_contact.name = "related"
+ @contact.contact = @related_contact
end
test "should serialize default root" do
@@ -256,4 +259,9 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<address>}, xml
assert_match %r{<apt-number type="integer">}, xml
end
+
+ test "association with sti" do
+ xml = @contact.to_xml(include: :contact)
+ assert xml.include?(%(<contact type="SerializableContact">))
+ end
end
diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb
new file mode 100644
index 0000000000..c05d71de5a
--- /dev/null
+++ b/activemodel/test/cases/validations/absence_validation_test.rb
@@ -0,0 +1,67 @@
+# encoding: utf-8
+require 'cases/helper'
+require 'models/topic'
+require 'models/person'
+require 'models/custom_reader'
+
+class AbsenceValidationTest < ActiveModel::TestCase
+ teardown do
+ Topic.reset_callbacks(:validate)
+ Person.reset_callbacks(:validate)
+ CustomReader.reset_callbacks(:validate)
+ end
+
+ def test_validate_absences
+ Topic.validates_absence_of(:title, :content)
+ t = Topic.new
+ t.title = "foo"
+ t.content = "bar"
+ assert t.invalid?
+ assert_equal ["must be blank"], t.errors[:title]
+ assert_equal ["must be blank"], t.errors[:content]
+ t.title = ""
+ t.content = "something"
+ assert t.invalid?
+ assert_equal ["must be blank"], t.errors[:content]
+ t.content = ""
+ assert t.valid?
+ end
+
+ def test_accepts_array_arguments
+ Topic.validates_absence_of %w(title content)
+ t = Topic.new
+ t.title = "foo"
+ t.content = "bar"
+ assert t.invalid?
+ assert_equal ["must be blank"], t.errors[:title]
+ assert_equal ["must be blank"], t.errors[:content]
+ end
+
+ def test_validates_acceptance_of_with_custom_error_using_quotes
+ Person.validates_absence_of :karma, message: "This string contains 'single' and \"double\" quotes"
+ p = Person.new
+ p.karma = "good"
+ assert p.invalid?
+ assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
+ end
+
+ def test_validates_absence_of_for_ruby_class
+ Person.validates_absence_of :karma
+ p = Person.new
+ p.karma = "good"
+ assert p.invalid?
+ assert_equal ["must be blank"], p.errors[:karma]
+ p.karma = nil
+ assert p.valid?
+ end
+
+ def test_validates_absence_of_for_ruby_class_with_custom_reader
+ CustomReader.validates_absence_of :karma
+ p = CustomReader.new
+ p[:karma] = "excellent"
+ assert p.invalid?
+ assert_equal ["must be blank"], p.errors[:karma]
+ p[:karma] = ""
+ assert p.valid?
+ end
+end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 113bfd6337..1a40ca8efc 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -375,4 +375,43 @@ class LengthValidationTest < ActiveModel::TestCase
t.author_name = "A very long author name that should still be valid." * 100
assert t.valid?
end
+
+ def test_validates_length_of_using_maximum_should_not_allow_nil_when_nil_not_allowed
+ Topic.validates_length_of :title, :maximum => 10, :allow_nil => false
+ t = Topic.new
+ assert t.invalid?
+ end
+
+ def test_validates_length_of_using_maximum_should_not_allow_nil_and_empty_string_when_blank_not_allowed
+ Topic.validates_length_of :title, :maximum => 10, :allow_blank => false
+ t = Topic.new
+ assert t.invalid?
+
+ t.title = ""
+ assert t.invalid?
+ end
+
+ def test_validates_length_of_using_both_minimum_and_maximum_should_not_allow_nil
+ Topic.validates_length_of :title, :minimum => 5, :maximum => 10
+ t = Topic.new
+ assert t.invalid?
+ end
+
+ def test_validates_length_of_using_minimum_0_should_not_allow_nil
+ Topic.validates_length_of :title, :minimum => 0
+ t = Topic.new
+ assert t.invalid?
+
+ t.title = ""
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_is_0_should_not_allow_nil
+ Topic.validates_length_of :title, :is => 0
+ t = Topic.new
+ assert t.invalid?
+
+ t.title = ""
+ assert t.valid?
+ end
end
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index 510c13a7c3..2f228cfa83 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -41,7 +41,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validates_acceptance_of_with_custom_error_using_quotes
- Person.validates_presence_of :karma, :message => "This string contains 'single' and \"double\" quotes"
+ Person.validates_presence_of :karma, message: "This string contains 'single' and \"double\" quotes"
p = Person.new
assert p.invalid?
assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
@@ -70,4 +70,38 @@ class PresenceValidationTest < ActiveModel::TestCase
p[:karma] = "Cold"
assert p.valid?
end
+
+ def test_validates_presence_of_with_allow_nil_option
+ Topic.validates_presence_of(:title, allow_nil: true)
+
+ t = Topic.new(title: "something")
+ assert t.valid?, t.errors.full_messages
+
+ t.title = ""
+ assert t.invalid?
+ assert_equal ["can't be blank"], t.errors[:title]
+
+ t.title = " "
+ assert t.invalid?, t.errors.full_messages
+ assert_equal ["can't be blank"], t.errors[:title]
+
+ t.title = nil
+ assert t.valid?, t.errors.full_messages
+ end
+
+ def test_validates_presence_of_with_allow_blank_option
+ Topic.validates_presence_of(:title, allow_blank: true)
+
+ t = Topic.new(title: "something")
+ assert t.valid?, t.errors.full_messages
+
+ t.title = ""
+ assert t.valid?, t.errors.full_messages
+
+ t.title = " "
+ assert t.valid?, t.errors.full_messages
+
+ t.title = nil
+ assert t.valid?, t.errors.full_messages
+ end
end
diff --git a/activemodel/test/models/observers.rb b/activemodel/test/models/observers.rb
deleted file mode 100644
index 3729b3435e..0000000000
--- a/activemodel/test/models/observers.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-class ORM
- include ActiveModel::Observing
-
- def save
- notify_observers :before_save
- end
-
- class Observer < ActiveModel::Observer
- def before_save_invocations
- @before_save_invocations ||= []
- end
-
- def before_save(record)
- before_save_invocations << record
- end
- end
-end
-
-class Widget < ORM; end
-class Budget < ORM; end
-class WidgetObserver < ORM::Observer; end
-class BudgetObserver < ORM::Observer; end
-class AuditTrail < ORM::Observer
- observe :widget, :budget
-end
-
-ORM.instantiate_observers
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 4fa2dcb847..3a0af57f64 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,208 @@
## Rails 4.0.0 (unreleased) ##
+* Support for PostgreSQL's `ltree` data type.
+
+ *Rob Worley*
+
+* Fix undefined method `to_i` when calling `new` on a scope that uses an Array.
+ Fixes #8718, #8734.
+
+ *Jason Stirk*
+
+* Rename `update_attributes` to `update`, keep `update_attributes` as an alias for `update` method.
+ This is a soft-deprecation for `update_attributes`, although it will still work without any
+ deprecation message in 4.0 is recommended to start using `update` since `update_attributes` will be
+ deprecated and removed in future versions of Rails.
+
+ *Amparo Luna + Guillermo Iguaran*
+
+* `after_commit` and `after_rollback` now validate the `:on` option and raise an `ArgumentError`
+ if it is not one of `:create`, `:destroy` or ``:update`
+
+ *Pascal Friederich*
+
+* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary.
+
+ * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given.
+ The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible).
+ The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default`
+
+ * New method `reversible` makes it possible to specify code to be run when migrating up or down.
+ See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method)
+
+ * New method `revert` will revert a whole migration or the given block.
+ If migrating down, the given migration / block is run normally.
+ See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations)
+
+ Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now
+ raise an `IrreversibleMigration` instead of actually executing them without any output.
+
+ *Marc-André Lafortune*
+
+* Serialized attributes can be serialized in integer columns.
+ Fix #8575.
+
+ *Rafael Mendonça França*
+
+* Keep index names when using `alter_table` with sqlite3.
+ Fix #3489.
+
+ *Yves Senn*
+
+* Add ability for postgresql adapter to disable user triggers in `disable_referential_integrity`.
+ Fix #5523.
+
+ *Gary S. Weaver*
+
+* Added support for `validates_uniqueness_of` in PostgreSQL array columns.
+ Fixes #8075.
+
+ *Pedro Padron*
+
+* Allow int4range and int8range columns to be created in PostgreSQL and properly convert to/from database.
+
+ *Alexey Vasiliev aka leopard*
+
+* Do not log the binding values for binary columns.
+
+ *Matthew M. Boedicker*
+
+* Fix counter cache columns not updated when replacing `has_many :through`
+ associations.
+
+ *Matthew Robertson*
+
+* Recognize migrations placed in directories containing numbers and 'rb'.
+ Fix #8492
+
+ *Yves Senn*
+
+* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control
+ the format of the timestamp value in the cache key.
+ This allows users to improve the precision of the cache key.
+ Fixes #8195.
+
+ *Rafael Mendonça França*
+
+* Add `:nsec` date format. This can be used to improve the precision of cache key.
+
+ *Jamie Gaskins*
+
+* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters
+ in the `variables: <hash>` parameter in `database.yml`. The key-value pairs of this
+ hash will be sent in a `SET key = value` query on new database connections. See also:
+ http://dev.mysql.com/doc/refman/5.0/en/set-statement.html
+ http://www.postgresql.org/docs/8.3/static/sql-set.html
+
+ *Aaron Stone*
+
+* Allow `Relation#where` with no arguments to be chained with new `not` query method.
+
+ Example:
+
+ Developer.where.not(name: 'Aaron')
+
+ *Akira Matsuda*
+
+* Unscope `update_column(s)` query to ignore default scope.
+
+ When applying `default_scope` to a class with a where clause, using
+ `update_column(s)` could generate a query that would not properly update
+ the record due to the where clause from the `default_scope` being applied
+ to the update query.
+
+ class User < ActiveRecord::Base
+ default_scope where(active: true)
+ end
+
+ user = User.first
+ user.active = false
+ user.save!
+
+ user.update_column(:active, true) # => false
+
+ In this situation we want to skip the default_scope clause and just
+ update the record based on the primary key. With this change:
+
+ user.update_column(:active, true) # => true
+
+ Fixes #8436.
+
+ *Carlos Antonio da Silva*
+
+* SQLite adapter no longer corrupts binary data if the data contains `%00`.
+
+ *Chris Feist*
+
+* Fix performance problem with `primary_key` method in PostgreSQL adapter when having many schemas.
+ Uses `pg_constraint` table instead of `pg_depend` table which has many records in general.
+ Fix #8414
+
+ *kennyj*
+
+* Do not instantiate intermediate Active Record objects when eager loading.
+ These records caused `after_find` to run more than expected.
+ Fix #3313
+
+ *Yves Senn*
+
+* Add STI support to init and building associations.
+ Allows you to do `BaseClass.new(type: "SubClass")` as well as
+ `parent.children.build(type: "SubClass")` or `parent.build_child`
+ to initialize an STI subclass. Ensures that the class name is a
+ valid class and that it is in the ancestors of the super class
+ that the association is expecting.
+
+ *Jason Rush*
+
+* Observers was extracted from Active Record as `rails-observers` gem.
+
+ *Rafael Mendonça França*
+
+* Ensure that associations take a symbol argument. *Steve Klabnik*
+
+* Fix dirty attribute checks for `TimeZoneConversion` with nil and blank
+ datetime attributes. Setting a nil datetime to a blank string should not
+ result in a change being flagged. Fix #8310
+
+ *Alisdair McDiarmid*
+
+* Prevent mass assignment to the type column of polymorphic associations when using `build`
+ Fix #8265
+
+ *Yves Senn*
+
+* Deprecate calling `Relation#sum` with a block. To perform a calculation over
+ the array result of the relation, use `to_a.sum(&block)`.
+
+ *Carlos Antonio da Silva*
+
+* Fix postgresql adapter to handle BC timestamps correctly
+
+ HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years)
+
+ *Bogdan Gusiev*
+
+* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is silently dropped.
+ Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits on these types.
+
+ *Victor Costan*
+
+* Don't change STI type when calling `ActiveRecord::Base#becomes`.
+ Add `ActiveRecord::Base#becomes!` with the previous behavior.
+
+ See #3023 for more information.
+
+ *Thomas Hollstegge*
+
+* `rename_index` can be used inside a `change_table` block.
+
+ change_table :accounts do |t|
+ t.rename_index :user_id, :account_id
+ end
+
+ *Jarek Radosz*
+
* `#pluck` can be used on a relation with `select` clause. Fix #7551
Example:
@@ -416,11 +619,11 @@
*kennyj*
-* Use inversed parent for first and last child of has_many association.
+* Use inversed parent for first and last child of `has_many` association.
*Ravil Bayramgalin*
-* Fix Column.microseconds and Column.fast_string_to_date to avoid converting
+* Fix `Column.microseconds` and `Column.fast_string_to_time` to avoid converting
timestamp seconds to a float, since it occasionally results in inaccuracies
with microsecond-precision times. Fixes #7352.
@@ -607,7 +810,7 @@
*kennyj*
-* Changed validates_presence_of on an association so that children objects
+* Changed `validates_presence_of` on an association so that children objects
do not validate as being present if they are marked for destruction. This
prevents you from saving the parent successfully and thus putting the parent
in an invalid state.
@@ -624,7 +827,7 @@
def change
create_table :foobars do |t|
- t.timestamps :precision => 0
+ t.timestamps precision: 0
end
end
@@ -710,13 +913,6 @@
*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.
@@ -726,7 +922,7 @@
end
person.pets.delete("1") # => [#<Pet id: 1>]
- person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
+ person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
*Francesco Rodriguez*
@@ -997,11 +1193,11 @@
Note that you do not need to explicitly specify references in the
following cases, as they can be automatically inferred:
- Post.where(comments: { name: 'foo' })
- Post.where('comments.name' => 'foo')
- Post.order('comments.name')
+ Post.includes(:comments).where(comments: { name: 'foo' })
+ Post.includes(:comments).where('comments.name' => 'foo')
+ Post.includes(:comments).order('comments.name')
- You also do not need to worry about this unless you are doing eager
+ You 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.
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index 03bde18130..0d7fb865e2 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2012 David Heinemeier Hansson
+Copyright (c) 2004-2013 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index cc8942809c..9fc6785d99 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -80,17 +80,6 @@ A short rundown of some of the major features:
{Learn more}[link:classes/ActiveRecord/Callbacks.html]
-* Observers that react to changes in a model.
-
- class CommentObserver < ActiveRecord::Observer
- def after_create(comment) # is called just after Comment#save
- CommentMailer.new_comment_email('david@loudthinking.com', comment).deliver
- end
- end
-
- {Learn more}[link:classes/ActiveRecord/Observer.html]
-
-
* Inheritance hierarchies.
class Company < ActiveRecord::Base; end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index cd9825b50c..ad12f8597f 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -143,7 +143,7 @@ Benchmark.ips(TIME) do |x|
end
x.report 'Resource#update' do
- Exhibit.first.update_attributes(:name => 'bob')
+ Exhibit.first.update(name: 'bob')
end
x.report 'Resource#destroy' do
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 45122539f1..c33f03f13f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2012 David Heinemeier Hansson
+# Copyright (c) 2004-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -45,7 +45,6 @@ module ActiveRecord
autoload :Migrator, 'active_record/migration'
autoload :ModelSchema
autoload :NestedAttributes
- autoload :Observer
autoload :Persistence
autoload :QueryCache
autoload :Querying
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 8101f7a45e..6acfec02c4 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -113,7 +113,7 @@ module ActiveRecord
# 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.
+ # object. Attempting to change it afterwards will result in a RuntimeError.
#
# 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
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 651b17920c..d8b6d7a86b 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -234,7 +234,7 @@ module ActiveRecord
# others.size | X | X | X
# others.length | X | X | X
# others.count | X | X | X
- # others.sum(args*,&block) | X | X | X
+ # others.sum(*args) | X | X | X
# others.empty? | X | X | X
# others.clear | X | X | X
# others.delete(other,other,...) | X | X | X
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 99e7383d42..3f0e4ca999 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -232,7 +232,8 @@ module ActiveRecord
def build_record(attributes)
reflection.build_association(attributes) do |record|
- attributes = create_scope.except(*(record.changed - [reflection.foreign_key]))
+ skip_assign = [reflection.foreign_key, reflection.type].compact
+ attributes = create_scope.except(*(record.changed - skip_assign))
record.assign_attributes(attributes)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 1df876bf62..5c37f42794 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -13,6 +13,8 @@ module ActiveRecord::Associations::Builder
end
def initialize(model, name, scope, options)
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
+
@model = model
@name = name
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 862ff201de..832b963052 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -34,7 +34,7 @@ module ActiveRecord
reload
end
- CollectionProxy.new(self)
+ CollectionProxy.new(klass, self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -161,15 +161,6 @@ module ActiveRecord
end
end
- # Calculate sum using SQL, not Enumerable.
- def sum(*args)
- if block_given?
- scope.sum(*args) { |*block_args| yield(*block_args) }
- else
- scope.sum(*args)
- end
- end
-
# Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
# association, it will be used for the query. Otherwise, construct options and pass them with
# scope to the target class's +count+.
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index e444b0ed83..7c43e37cf2 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -30,9 +30,9 @@ module ActiveRecord
class CollectionProxy < Relation
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
- def initialize(association) #:nodoc:
+ def initialize(klass, association) #:nodoc:
@association = association
- super association.klass, association.klass.arel_table
+ super klass, klass.arel_table
merge! association.scope(nullify: false)
end
@@ -101,7 +101,7 @@ module ActiveRecord
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
#
- # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
+ # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
# # => [
# # #<Pet id: 2, name: "Spook">,
# # #<Pet id: 3, name: "Choo-Choo">
@@ -672,7 +672,11 @@ module ActiveRecord
end
# Returns the size of the collection. If the collection hasn't been loaded,
- # it executes a <tt>SELECT COUNT(*)</tt> query.
+ # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
+ #
+ # If the collection has been already loaded +size+ and +length+ are
+ # equivalent. If not and you are going to need the records anyway
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -697,7 +701,8 @@ module ActiveRecord
# Returns the size of the collection calling +size+ on the target.
# If the collection has been already loaded, +length+ and +size+ are
- # equivalent.
+ # equivalent. If not and you are going to need the records anyway this
+ # method will take one less query. Otherwise +size+ is more efficient.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -718,7 +723,12 @@ module ActiveRecord
@association.length
end
- # Returns +true+ if the collection is empty.
+ # Returns +true+ if the collection is empty. If the collection has been
+ # loaded or the <tt>:counter_sql</tt> option is provided, it is equivalent
+ # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
+ # it is equivalent to <tt>collection.exists?</tt>. If the collection has
+ # not already been loaded and you are going to fetch the records anyway it
+ # is better to check <tt>collection.length.zero?</tt>.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -814,7 +824,7 @@ module ActiveRecord
#
# person.pets # => [#<Pet id: 20, name: "Snoop">]
#
- # person.pets.include?(Pet.find(20)) # => true
+ # person.pets.include?(Pet.find(20)) # => true
# person.pets.include?(Pet.find(21)) # => false
def include?(record)
@association.include?(record)
@@ -961,7 +971,7 @@ module ActiveRecord
# person.pets.reload # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
#
- # person.pets(true)  # fetches pets from the database
+ # person.pets(true) # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
proxy_association.reload
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index c7d8a84a7e..c3266f2bb4 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -153,6 +153,11 @@ module ActiveRecord
delete_through_records(records)
+ if source_reflection.options[:counter_cache]
+ counter = source_reflection.counter_cache_column
+ klass.decrement_counter counter, records.map(&:id)
+ end
+
if through_reflection.macro == :has_many && update_through_counter?(method)
update_counter(-count, through_reflection)
end
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index fdf8ae1453..08e0ec691f 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -23,7 +23,7 @@ module ActiveRecord
attributes = construct_join_attributes(record)
if through_record
- through_record.update_attributes(attributes)
+ through_record.update(attributes)
elsif owner.new_record?
through_proxy.build(attributes)
else
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 0848e7afb3..82bf426b22 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -100,7 +100,9 @@ module ActiveRecord
case association
when Hash
preload_hash(association)
- when String, Symbol
+ when Symbol
+ preload_one(association)
+ when String
preload_one(association.to_sym)
else
raise ArgumentError, "#{association.inspect} was not recognised for preload"
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 6c5e2ac05d..ecfa556ab4 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -132,7 +132,7 @@ module ActiveRecord
if object.class.send(:create_time_zone_conversion_attribute?, name, column)
Time.zone.local(*set_values)
else
- Time.time_with_datetime_fallback(object.class.default_timezone, *set_values)
+ Time.send(object.class.default_timezone, *set_values)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 0333605eac..7e357aa2f4 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -71,11 +71,11 @@ module ActiveRecord
super(attr, value)
end
- def update(*)
+ def update_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
- def create(*)
+ def create_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 90701938e5..3c03cce838 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -32,21 +32,29 @@ module ActiveRecord
protected
- # We want to generate the methods via module_eval rather than define_method,
- # because define_method is slower on dispatch and uses more memory (because it
- # creates a closure).
+ # We want to generate the methods via module_eval rather than
+ # define_method, because define_method is slower on dispatch and
+ # uses more memory (because it creates a closure).
#
- # But sometimes the database might return columns with characters that are not
- # allowed in normal method names (like 'my_column(omg)'. So to work around this
- # we first define with the __temp__ identifier, and then use alias method to
- # rename it to what we want.
- def define_method_attribute(attr_name)
+ # But sometimes the database might return columns with
+ # characters that are not allowed in normal method names (like
+ # 'my_column(omg)'. So to work around this we first define with
+ # the __temp__ identifier, and then use alias method to rename
+ # it to what we want.
+ #
+ # We are also defining a constant to hold the frozen string of
+ # the attribute name. Using a constant means that we do not have
+ # to allocate an object on each call to the attribute method.
+ # Making it frozen means that it doesn't get duped when used to
+ # key the @attributes_cache in read_attribute.
+ def define_method_attribute(name)
+ safe_name = name.unpack('h*').first
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__
- read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }
+ def __temp__#{safe_name}
+ read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
end
- alias_method '#{attr_name}', :__temp__
- undef_method :__temp__
+ alias_method #{name.inspect}, :__temp__#{safe_name}
+ undef_method :__temp__#{safe_name}
STR
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 47d4a938af..25d62fdb85 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -112,6 +112,14 @@ module ActiveRecord
end
end
+ def _field_changed?(attr, old, value)
+ if self.class.serialized_attributes.include?(attr)
+ old != value
+ else
+ super
+ end
+ end
+
def read_attribute_before_type_cast(attr_name)
if self.class.serialized_attributes.include?(attr_name)
super.unserialized_value
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 427c61079a..47a8b576c0 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -34,6 +34,7 @@ module ActiveRecord
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
method_body, line = <<-EOV, __LINE__ + 1
def #{attr_name}=(original_time)
+ original_time = nil if original_time.blank?
time = original_time
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index fa9097db1f..cd33494cc3 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -9,15 +9,19 @@ module ActiveRecord
module ClassMethods
protected
- def define_method_attribute=(attr_name)
- if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
- generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
- else
- generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
- write_attribute(attr_name, new_value)
- end
+
+ # See define_method_attribute in read.rb for an explanation of
+ # this code.
+ def define_method_attribute=(name)
+ safe_name = name.unpack('h*').first
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__#{safe_name}=(value)
+ write_attribute(AttrNames::ATTR_#{safe_name}, value)
end
- end
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
+ undef_method :__temp__#{safe_name}=
+ STR
+ end
end
# Updates the attribute identified by <tt>attr_name</tt> with the
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 907fe70522..704998301c 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -32,8 +32,6 @@ module ActiveRecord
# autosave callbacks are executed. Placing your callbacks after
# associations is usually a good practice.
#
- # == Examples
- #
# === One-to-one Example
#
# class Post
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 5eacb8f143..aab832c2f7 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -13,6 +13,7 @@ require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/class/subclasses'
require 'arel'
require 'active_record/errors'
require 'active_record/log_subscriber'
@@ -320,7 +321,6 @@ module ActiveRecord #:nodoc:
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
- extend ActiveModel::Observing::ClassMethods
extend ActiveModel::Naming
extend ActiveSupport::Benchmarkable
@@ -348,7 +348,6 @@ module ActiveRecord #:nodoc:
include Locking::Pessimistic
include AttributeMethods
include Callbacks
- include ActiveModel::Observing
include Timestamp
include Associations
include ActiveModel::SecurePassword
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 1c9c627090..22226b2f4f 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -299,11 +299,11 @@ module ActiveRecord
run_callbacks(:save) { super }
end
- def create #:nodoc:
+ def create_record #:nodoc:
run_callbacks(:create) { super }
end
- def update(*) #:nodoc:
+ def update_record(*) #:nodoc:
run_callbacks(:update) { super }
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index db0db272a6..82d0cf7e2e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,4 +1,5 @@
require 'thread'
+require 'thread_safe'
require 'monitor'
require 'set'
require 'active_support/deprecation'
@@ -236,9 +237,6 @@ module ActiveRecord
@spec = spec
- # The cache of reserved connections mapped to threads
- @reserved_connections = {}
-
@checkout_timeout = spec.config[:checkout_timeout] || 5
@dead_connection_timeout = spec.config[:dead_connection_timeout]
@reaper = Reaper.new self, spec.config[:reaping_frequency]
@@ -247,6 +245,9 @@ module ActiveRecord
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ # The cache of reserved connections mapped to threads
+ @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
+
@connections = []
@automatic_reconnect = true
@@ -267,7 +268,9 @@ module ActiveRecord
# #connection can be called any number of times; the connection is
# held in a hash keyed by the thread id.
def connection
- synchronize do
+ # this is correctly done double-checked locking
+ # (ThreadSafe::Cache's lookups have volatile semantics)
+ @reserved_connections[current_connection_id] || synchronize do
@reserved_connections[current_connection_id] ||= checkout
end
end
@@ -310,7 +313,7 @@ module ActiveRecord
# Disconnects all connections in the pool, and clears the pool.
def disconnect!
synchronize do
- @reserved_connections = {}
+ @reserved_connections.clear
@connections.each do |conn|
checkin conn
conn.disconnect!
@@ -323,7 +326,7 @@ module ActiveRecord
# Clears the cache which maps classes.
def clear_reloadable_connections!
synchronize do
- @reserved_connections = {}
+ @reserved_connections.clear
@connections.each do |conn|
checkin conn
conn.disconnect! if conn.requires_reloading?
@@ -490,8 +493,15 @@ module ActiveRecord
# determine the connection pool that they should use.
class ConnectionHandler
def initialize
- @owner_to_pool = Hash.new { |h,k| h[k] = {} }
- @class_to_pool = Hash.new { |h,k| h[k] = {} }
+ # These caches are keyed by klass.name, NOT klass. Keying them by klass
+ # alone would lead to memory leaks in development mode as all previous
+ # instances of the class would stay in memory.
+ @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
+ h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
+ end
+ @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
+ h[k] = ThreadSafe::Cache.new
+ end
end
def connection_pool_list
@@ -508,7 +518,7 @@ module ActiveRecord
def establish_connection(owner, spec)
@class_to_pool.clear
- owner_to_pool[owner] = ConnectionAdapters::ConnectionPool.new(spec)
+ owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
end
# Returns true if there are any active connections among the connection
@@ -554,7 +564,7 @@ module ActiveRecord
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
def remove_connection(owner)
- if pool = owner_to_pool.delete(owner)
+ if pool = owner_to_pool.delete(owner.name)
@class_to_pool.clear
pool.automatic_reconnect = false
pool.disconnect!
@@ -572,13 +582,13 @@ module ActiveRecord
# but that's ok since the nil case is not the common one that we wish to optimise
# for.
def retrieve_connection_pool(klass)
- class_to_pool[klass] ||= begin
+ class_to_pool[klass.name] ||= begin
until pool = pool_for(klass)
klass = klass.superclass
break unless klass <= Base
end
- class_to_pool[klass] = pool
+ class_to_pool[klass.name] = pool
end
end
@@ -593,21 +603,21 @@ module ActiveRecord
end
def pool_for(owner)
- owner_to_pool.fetch(owner) {
+ owner_to_pool.fetch(owner.name) {
if ancestor_pool = pool_from_any_process_for(owner)
# A connection was established in an ancestor process that must have
# subsequently forked. We can't reuse the connection, but we can copy
# the specification and establish a new connection with it.
establish_connection owner, ancestor_pool.spec
else
- owner_to_pool[owner] = nil
+ owner_to_pool[owner.name] = nil
end
}
end
def pool_from_any_process_for(owner)
- owner_to_pool = @owner_to_pool.values.find { |v| v[owner] }
- owner_to_pool && owner_to_pool[owner]
+ owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
+ owner_to_pool && owner_to_pool[owner.name]
end
end
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 4f3eebce7d..c3d15ca929 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -287,7 +287,7 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- columns = Hash[columns(table_name).map { |c| [c.name, c] }]
+ columns = schema_cache.columns_hash(table_name)
key_list = []
value_list = fixture.map do |name, value|
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 38960ab873..b1ec33d06c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -190,16 +190,16 @@ module ActiveRecord
#
# What can be written like this with the regular calls to column:
#
- # create_table "products", force: true do |t|
- # t.column "shop_id", :integer
- # t.column "creator_id", :integer
- # t.column "name", :string, default: "Untitled"
- # t.column "value", :string, default: "Untitled"
- # t.column "created_at", :datetime
- # t.column "updated_at", :datetime
+ # create_table :products do |t|
+ # t.column :shop_id, :integer
+ # t.column :creator_id, :integer
+ # t.column :name, :string, default: "Untitled"
+ # t.column :value, :string, default: "Untitled"
+ # t.column :created_at, :datetime
+ # t.column :updated_at, :datetime
# end
#
- # Can also be written as follows using the short-hand:
+ # can also be written as follows using the short-hand:
#
# create_table :products do |t|
# t.integer :shop_id, :creator_id
@@ -324,6 +324,7 @@ module ActiveRecord
# change_table :table do |t|
# t.column
# t.index
+ # t.rename_index
# t.timestamps
# t.change
# t.change_default
@@ -386,6 +387,13 @@ module ActiveRecord
@base.index_exists?(@table_name, column_name, options)
end
+ # Renames the given index on the table.
+ #
+ # t.rename_index(:user_id, :account_id)
+ def rename_index(index_name, new_index_name)
+ @base.rename_index(@table_name, index_name, new_index_name)
+ end
+
# Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
#
# t.timestamps
@@ -415,7 +423,7 @@ module ActiveRecord
# t.remove(:qualification)
# t.remove(:qualification, :experience)
def remove(*column_names)
- @base.remove_column(@table_name, *column_names)
+ @base.remove_columns(@table_name, *column_names)
end
# Removes the given index from the table.
@@ -482,20 +490,8 @@ module ActiveRecord
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{column_type}(*args) # def string(*args)
options = args.extract_options! # options = args.extract_options!
- column_names = args # column_names = args
- type = :'#{column_type}' # type = :string
- column_names.each do |name| # column_names.each do |name|
- column = ColumnDefinition.new(@base, name.to_s, type) # column = ColumnDefinition.new(@base, name, type)
- if options[:limit] # if options[:limit]
- column.limit = options[:limit] # column.limit = options[:limit]
- elsif native[type].is_a?(Hash) # elsif native[type].is_a?(Hash)
- column.limit = native[type][:limit] # column.limit = native[type][:limit]
- end # end
- column.precision = options[:precision] # column.precision = options[:precision]
- column.scale = options[:scale] # column.scale = options[:scale]
- column.default = options[:default] # column.default = options[:default]
- column.null = options[:null] # column.null = options[:null]
- @base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
+ args.each do |name| # column_names.each do |name|
+ @base.add_column(@table_name, name, :#{column_type}, options) # @base.add_column(@table_name, name, :string, options)
end # end
end # end
EOV
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 17dd71e898..cdc8433185 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -214,6 +214,17 @@ module ActiveRecord
end
end
+ # Drops the join table specified by the given arguments.
+ # See create_join_table for details.
+ #
+ # Although this command ignores the block if one is given, it can be helpful
+ # to provide one in a migration's +change+ method so it can be reverted.
+ # In that case, the block will be used by create_join_table.
+ def drop_join_table(table_1, table_2, options = {})
+ join_table_name = find_join_table_name(table_1, table_2, options)
+ drop_table(join_table_name)
+ end
+
# A block for changing columns in +table+.
#
# # change_table() yields a Table instance
@@ -294,6 +305,10 @@ module ActiveRecord
end
# Drops a table from the database.
+ #
+ # Although this command ignores +options+ and the block if one is given, it can be helpful
+ # to provide these in a migration's +change+ method so it can be reverted.
+ # In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
execute "DROP TABLE #{quote_table_name(table_name)}"
end
@@ -306,14 +321,26 @@ module ActiveRecord
execute(add_column_sql)
end
- # Removes the column(s) from the table definition.
+ # Removes the given columns from the table definition.
#
- # remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience)
- def remove_column(table_name, *column_names)
- columns_for_remove(table_name, *column_names).each {|column_name| execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}" }
+ def remove_columns(table_name, *column_names)
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
+ column_names.each do |column_name|
+ remove_column(table_name, column_name)
+ end
+ end
+
+ # Removes the column from the table definition.
+ #
+ # remove_column(:suppliers, :qualification)
+ #
+ # The +type+ and +options+ parameters will be ignored if present. It can be helpful
+ # to provide these in a migration's +change+ method so it can be reverted.
+ # In that case, +type+ and +options+ will be used by add_column.
+ def remove_column(table_name, column_name, type = nil, options = {})
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
end
- alias :remove_columns :remove_column
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
@@ -540,7 +567,7 @@ module ActiveRecord
column_type_sql << "(#{precision})"
end
elsif scale
- raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
@@ -662,7 +689,8 @@ module ActiveRecord
end
def columns_for_remove(table_name, *column_names)
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
+ ActiveSupport::Deprecation.warn("columns_for_remove is deprecated and will be removed in the future")
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.blank?
column_names.map {|column_name| quote_column_name(column_name) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 84e73e6f0f..52b0b3fe79 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -14,7 +14,7 @@ module ActiveRecord
end
def extract_default(default)
- if sql_type =~ /blob/i || type == :text
+ if blob_or_text_column?
if default.blank?
null || strict ? nil : ''
else
@@ -28,9 +28,13 @@ module ActiveRecord
end
def has_default?
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
+ return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
super
end
+
+ def blob_or_text_column?
+ sql_type =~ /blob/i || type == :text
+ end
# Must return the relevant concrete adapter
def adapter
@@ -669,10 +673,13 @@ module ActiveRecord
rename_column_sql
end
- def remove_column_sql(table_name, *column_names)
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
+ def remove_column_sql(table_name, column_name, type = nil, options = {})
+ "DROP #{quote_column_name(column_name)}"
+ end
+
+ def remove_columns_sql(table_name, *column_names)
+ column_names.map {|column_name| remove_column_sql(table_name, column_name) }
end
- alias :remove_columns_sql :remove_column
def add_index_sql(table_name, column_name, options = {})
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
@@ -704,6 +711,45 @@ module ActiveRecord
end
column
end
+
+ def configure_connection
+ variables = @config[:variables] || {}
+
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ variables[:sql_auto_is_null] = 0
+
+ # Increase timeout so the server doesn't disconnect us.
+ wait_timeout = @config[:wait_timeout]
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
+ variables[:wait_timeout] = wait_timeout
+
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
+ # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
+ # If the user has provided another value for sql_mode, don't replace it.
+ if strict_mode? && !variables.has_key?(:sql_mode)
+ variables[:sql_mode] = 'STRICT_ALL_TABLES'
+ end
+
+ # NAMES does not have an equals sign, see
+ # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
+ # (trailing comma because variable_assignments will always have content)
+ encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
+
+ # Gather up all of the SET variables...
+ variable_assignments = variables.map do |k, v|
+ if v == ':default' || v == :default
+ "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
+ elsif !v.nil?
+ "@@SESSION.#{k.to_s} = #{quote(v)}"
+ end
+ # or else nil; compact to clear nils out
+ end.compact.join(', ')
+
+ # ...and send them all in one query
+ execute("SET #{encoding} #{variable_assignments}", :skip_logging)
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 80984f39c9..51d3acaff8 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -126,6 +126,7 @@ module ActiveRecord
when :hstore then "#{klass}.string_to_hstore(#{var_name})"
when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
when :json then "#{klass}.string_to_json(#{var_name})"
+ when :intrange then "#{klass}.string_to_intrange(#{var_name})"
else var_name
end
end
@@ -205,7 +206,11 @@ module ActiveRecord
when TrueClass, FalseClass
value ? 1 : 0
else
- value.to_i
+ if value.respond_to?(:to_i)
+ value.to_i
+ else
+ nil
+ end
end
end
@@ -240,7 +245,7 @@ module ActiveRecord
# Treat 0000-00-00 00:00:00 as nil.
return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
- Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
end
def fast_string_to_date(string)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index f55d19393c..a6013f754a 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -251,27 +251,7 @@ module ActiveRecord
def configure_connection
@connection.query_options.merge!(:as => :array)
-
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- variable_assignments = ['SQL_AUTO_IS_NULL=0']
-
- # Make MySQL reject illegal values rather than truncating or
- # blanking them. See
- # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
- variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'" if strict_mode?
-
- encoding = @config[:encoding]
-
- # make sure we set the encoding
- variable_assignments << "NAMES '#{encoding}'" if encoding
-
- # increase timeout so mysql server doesn't disconnect us
- wait_timeout = @config[:wait_timeout]
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
- variable_assignments << "@@wait_timeout = #{wait_timeout}"
-
- execute("SET #{variable_assignments.join(', ')}", :skip_logging)
+ super
end
def version
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 9627aaae3a..631f646f58 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -2,7 +2,7 @@ require 'active_record/connection_adapters/abstract_mysql_adapter'
require 'active_record/connection_adapters/statement_pool'
require 'active_support/core_ext/hash/keys'
-gem 'mysql', '~> 2.9.0'
+gem 'mysql', '~> 2.9'
require 'mysql'
class Mysql
@@ -51,7 +51,8 @@ module ActiveRecord
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
- # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html)
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html)
+ # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -535,18 +536,10 @@ module ActiveRecord
configure_connection
end
+ # Many Rails applications monkey-patch a replacement of the configure_connection method
+ # and don't call 'super', so leave this here even though it looks superfluous.
def configure_connection
- encoding = @config[:encoding]
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
-
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
-
- # Make MySQL reject illegal values rather than truncating or
- # blanking them. See
- # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
- execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging) if strict_mode?
+ super
end
def select(sql, name = nil, binds = [])
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 62d091357d..f7d734a2f1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -8,6 +8,8 @@ module ActiveRecord
case string
when 'infinity'; 1.0 / 0.0
when '-infinity'; -1.0 / 0.0
+ when / BC$/
+ super("-" + string.sub(/ BC$/, ""))
else
super
end
@@ -90,6 +92,36 @@ module ActiveRecord
parse_pg_array(string).map{|val| oid.type_cast val}
end
+ def string_to_intrange(string)
+ if string.nil?
+ nil
+ elsif "empty" == string
+ (nil..nil)
+ elsif String === string && (matches = /^(\(|\[)([0-9]+),(\s?)([0-9]+)(\)|\])$/i.match(string))
+ lower_bound = ("(" == matches[1] ? (matches[2].to_i + 1) : matches[2].to_i)
+ upper_bound = (")" == matches[5] ? (matches[4].to_i - 1) : matches[4].to_i)
+ (lower_bound..upper_bound)
+ else
+ string
+ end
+ end
+
+ def intrange_to_string(object)
+ if object.nil?
+ nil
+ elsif Range === object
+ if [object.first, object.last].all? { |el| Integer === el }
+ "[#{object.first.to_i},#{object.exclude_end? ? object.last.to_i : object.last.to_i + 1})"
+ elsif [object.first, object.last].all? { |el| NilClass === el }
+ "empty"
+ else
+ nil
+ end
+ else
+ object
+ end
+ end
+
private
HstorePair = begin
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 52344f61c0..02c295983f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -82,7 +82,7 @@ module ActiveRecord
def type_cast(value)
return if value.nil?
- value.to_i rescue value ? 1 : 0
+ ConnectionAdapters::Column.value_to_integer value
end
end
@@ -168,6 +168,14 @@ module ActiveRecord
end
end
+ class IntRange < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_intrange value
+ end
+ end
+
class TypeMap
def initialize
@mapping = {}
@@ -268,6 +276,10 @@ module ActiveRecord
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
register_type 'json', OID::Json.new
+ register_type 'ltree', OID::Identity.new
+
+ register_type 'int4range', OID::IntRange.new
+ alias_type 'int8range', 'int4range'
register_type 'cidr', OID::Cidr.new
alias_type 'inet', 'cidr'
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 9d3fa18e3a..c2fcef94da 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -31,6 +31,11 @@ module ActiveRecord
when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
+ when Range
+ case column.sql_type
+ when 'int4range', 'int8range' then super(PostgreSQLColumn.intrange_to_string(value), column)
+ else super
+ end
when IPAddr
case column.sql_type
when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
@@ -89,6 +94,11 @@ module ActiveRecord
when 'json' then PostgreSQLColumn.json_to_string(value)
else super(value, column)
end
+ when Range
+ case column.sql_type
+ when 'int4range', 'int8range' then PostgreSQLColumn.intrange_to_string(value)
+ else super(value, column)
+ end
when IPAddr
return super(value, column) unless ['inet','cidr'].include? column.sql_type
PostgreSQLColumn.cidr_to_string(value)
@@ -129,11 +139,15 @@ module ActiveRecord
# Quote date/time values for use in SQL input. Includes microseconds
# if the value is a Time responding to usec.
def quoted_date(value) #:nodoc:
+ result = super
if value.acts_like?(:time) && value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
+ result = "#{result}.#{sprintf("%06d", value.usec)}"
+ end
+
+ if value.year < 0
+ result = result.sub(/^-/, "") + " BC"
end
+ result
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 16da3ea732..bc775394a6 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -7,13 +7,21 @@ module ActiveRecord
end
def disable_referential_integrity #:nodoc:
- if supports_disable_referential_integrity? then
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
+ if supports_disable_referential_integrity?
+ begin
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
+ rescue
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
+ end
end
yield
ensure
- if supports_disable_referential_integrity? then
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
+ if supports_disable_referential_integrity?
+ begin
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
+ rescue
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 82a0b662f4..e10b562fa4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -267,7 +267,6 @@ module ActiveRecord
FROM pg_class seq,
pg_attribute attr,
pg_depend dep,
- pg_namespace name,
pg_constraint cons
WHERE seq.oid = dep.objid
AND seq.relkind = 'S'
@@ -306,12 +305,11 @@ module ActiveRecord
# Returns just a table's primary key
def primary_key(table)
row = exec_query(<<-end_sql, 'SCHEMA').rows.first
- SELECT DISTINCT(attr.attname)
+ SELECT 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 = '#{quote_table_name(table)}'::regclass
+ AND cons.conrelid = '#{quote_table_name(table)}'::regclass
end_sql
row && row.first
@@ -396,6 +394,13 @@ module ActiveRecord
when nil, 0..0x3fffffff; super(type)
else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
end
+ when 'text'
+ # PostgreSQL doesn't support limits on text columns.
+ # The hard limit is 1Gb, according to section 8.3 in the manual.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.")
+ end
when 'integer'
return 'integer' unless limit
@@ -412,6 +417,14 @@ module ActiveRecord
when 0..6; "timestamp(#{precision})"
else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6")
end
+ when 'intrange'
+ return 'int4range' unless limit
+
+ case limit
+ when 1..4; 'int4range'
+ when 5..8; 'int8range'
+ else raise(ActiveRecordError, "No range type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ end
else
super
end
@@ -422,20 +435,17 @@ module ActiveRecord
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
# requires that the ORDER BY include the distinct column.
#
- # distinct("posts.id", "posts.created_at desc")
+ # distinct("posts.id", ["posts.created_at desc"])
+ # # => "DISTINCT posts.id, posts.created_at AS alias_0"
def distinct(columns, orders) #:nodoc:
- return "DISTINCT #{columns}" if orders.empty?
-
- # Construct a clean list of column names from the ORDER BY clause, removing
- # any ASC/DESC modifiers
- order_columns = orders.collect do |s|
- s = s.to_sql unless s.is_a?(String)
- s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
- end
- order_columns.delete_if { |c| c.blank? }
- order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
-
- "DISTINCT #{columns}, #{order_columns * ', '}"
+ order_columns = orders.map{ |s|
+ # Convert Arel node to string
+ s = s.to_sql unless s.is_a?(String)
+ # Remove any ASC/DESC modifiers
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
+
+ [super].concat(order_columns).join(', ')
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e18464fa35..72e476be90 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -24,7 +24,7 @@ module ActiveRecord
# Forward any unused config params to PGconn.connect.
[:statement_limit, :encoding, :min_messages, :schema_search_path,
:schema_order, :adapter, :pool, :checkout_timeout, :template,
- :reaping_frequency, :insert_returning].each do |key|
+ :reaping_frequency, :insert_returning, :variables].each do |key|
conn_params.delete key
end
conn_params.delete_if { |k,v| v.nil? }
@@ -114,6 +114,9 @@ module ActiveRecord
# JSON
when /\A'(.*)'::json\z/
$1
+ # int4range, int8range
+ when /\A'(.*)'::int(4|8)range\z/
+ $1
# Object identifier types
when /\A-?\d+\z/
$1
@@ -170,6 +173,8 @@ module ActiveRecord
:decimal
when 'hstore'
:hstore
+ when 'ltree'
+ :ltree
# Network address types
when 'inet'
:inet
@@ -209,9 +214,12 @@ module ActiveRecord
# UUID type
when 'uuid'
:uuid
- # JSON type
- when 'json'
- :json
+ # JSON type
+ when 'json'
+ :json
+ # int4range, int8range types
+ when 'int4range', 'int8range'
+ :intrange
# Small and big integer types
when /^(?:small|big)int$/
:integer
@@ -238,6 +246,8 @@ module ActiveRecord
# <encoding></tt> call on the connection.
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
# * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
# defaults to true.
#
@@ -267,6 +277,10 @@ module ActiveRecord
column(name, 'hstore', options)
end
+ def ltree(name, options = {})
+ column(name, 'ltree', options)
+ end
+
def inet(name, options = {})
column(name, 'inet', options)
end
@@ -287,6 +301,10 @@ module ActiveRecord
column(name, 'json', options)
end
+ def intrange(name, options = {})
+ column(name, 'intrange', options)
+ end
+
def column(name, type = nil, options = {})
super
column = self[name]
@@ -327,7 +345,9 @@ module ActiveRecord
cidr: { name: "cidr" },
macaddr: { name: "macaddr" },
uuid: { name: "uuid" },
- json: { name: "json" }
+ json: { name: "json" },
+ intrange: { name: "int4range" },
+ ltree: { name: "ltree" }
}
include Quoting
@@ -627,32 +647,30 @@ module ActiveRecord
end
def exec_cache(sql, binds)
+ stmt_key = prepare_statement sql
+
+ # Clear the queue
+ @connection.get_last_result
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
+ type_cast(val, col)
+ })
+ @connection.block
+ @connection.get_last_result
+ rescue PGError => e
+ # Get the PG code for the failure. Annoyingly, the code for
+ # prepared statements whose return value may have changed is
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
begin
- stmt_key = prepare_statement sql
-
- # Clear the queue
- @connection.get_last_result
- @connection.send_query_prepared(stmt_key, binds.map { |col, val|
- type_cast(val, col)
- })
- @connection.block
- @connection.get_last_result
- rescue PGError => e
- # Get the PG code for the failure. Annoyingly, the code for
- # prepared statements whose return value may have changed is
- # FEATURE_NOT_SUPPORTED. Check here for more details:
- # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- begin
- code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
- rescue
- raise e
- end
- if FEATURE_NOT_SUPPORTED == code
- @statements.delete sql_key(sql)
- retry
- else
- raise e
- end
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ rescue
+ raise e
+ end
+ if FEATURE_NOT_SUPPORTED == code
+ @statements.delete sql_key(sql)
+ retry
+ else
+ raise e
end
end
@@ -706,11 +724,24 @@ module ActiveRecord
# If using Active Record's time zone support configure the connection to return
# TIMESTAMP WITH ZONE types in UTC.
+ # (SET TIME ZONE does not use an equals sign like other SET variables)
if ActiveRecord::Base.default_timezone == :utc
execute("SET time zone 'UTC'", 'SCHEMA')
elsif @local_tz
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
end
+
+ # SET statements from :variables config hash
+ # http://www.postgresql.org/docs/8.3/static/sql-set.html
+ variables = @config[:variables] || {}
+ variables.map do |k, v|
+ if v == ':default' || v == :default
+ # Sets the value to the global or compile default
+ execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
+ elsif !v.nil?
+ execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
+ end
+ end
end
# Returns the current ID of a table's sequence.
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index aad1f9a7ef..5839d1d3b4 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module ConnectionAdapters
class SchemaCache
- attr_reader :columns, :columns_hash, :primary_keys, :tables, :version
+ attr_reader :primary_keys, :tables, :version
attr_accessor :connection
def initialize(conn)
@@ -30,6 +30,25 @@ module ActiveRecord
end
end
+ # Get the columns for a table
+ def columns(table = nil)
+ if table
+ @columns[table]
+ else
+ @columns
+ end
+ end
+
+ # Get the columns for a table as a hash, key is the column name
+ # value is the column object.
+ def columns_hash(table = nil)
+ if table
+ @columns_hash[table]
+ else
+ @columns_hash
+ end
+ end
+
# Clears out internal caches
def clear!
@columns.clear
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index b89e9a01a8..11e8197293 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -444,15 +444,11 @@ module ActiveRecord
end
end
- def remove_column(table_name, *column_names) #:nodoc:
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
- column_names.each do |column_name|
- alter_table(table_name) do |definition|
- definition.columns.delete(definition[column_name])
- end
+ def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
+ alter_table(table_name) do |definition|
+ definition.columns.delete(definition[column_name])
end
end
- alias :remove_columns :remove_column
def change_column_default(table_name, column_name, default) #:nodoc:
alter_table(table_name) do |definition|
@@ -537,7 +533,6 @@ module ActiveRecord
end
yield @definition if block_given?
end
-
copy_table_indexes(from, to, options[:rename] || {})
copy_table_contents(from, to,
@definition.columns.map {|column| column.name},
@@ -560,7 +555,7 @@ module ActiveRecord
unless columns.empty?
# index name can't be the same
- opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
+ opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_") }
opts[:unique] = true if index.unique
add_index(to, columns, opts)
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 957027c1ee..94c6684700 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -83,7 +83,12 @@ module ActiveRecord
@attribute_methods_mutex = Mutex.new
# force attribute methods to be higher in inheritance hierarchy than other generated methods
- generated_attribute_methods
+ generated_attribute_methods.const_set(:AttrNames, Module.new {
+ def self.const_missing(name)
+ const_set(name, [name.to_s.sub(/ATTR_/, '')].pack('h*').freeze)
+ end
+ })
+
generated_feature_methods
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index c53b7b3e78..81f92db271 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -79,16 +79,17 @@ module ActiveRecord
where(primary_key => id).update_all updates.join(', ')
end
- # Increment a number field by one, usually representing a count.
+ # Increment a numeric field by one, via a direct SQL update.
#
- # 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.
+ # This method is used primarily for maintaining counter_cache columns used to
+ # store aggregate values. For example, a DiscussionBoard may cache posts_count
+ # and comments_count to avoid running an SQL query to calculate the number of
+ # posts and comments there are each time it is displayed.
#
# ==== Parameters
#
# * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
+ # * +id+ - The id of the object that should be incremented or an Array of ids.
#
# ==== Examples
#
@@ -98,14 +99,15 @@ module ActiveRecord
update_counters(id, counter_name => 1)
end
- # Decrement a number field by one, usually representing a count.
+ # Decrement a numeric field by one, via a direct SQL update.
#
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ # 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.
+ # * +id+ - The id of the object that should be decremented or an Array of ids.
#
# ==== Examples
#
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index af772996f1..70683eb731 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -6,11 +6,12 @@ module ActiveRecord
base.mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
end
- # 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
- # whole. That is, the threshold is not checked against each individual
- # query, but against the duration of the entire block. This approach is
- # convenient for relations.
+ # If the database adapter supports explain and 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 whole. That is, the
+ # threshold is not checked against each individual query, but against the
+ # duration of the entire block. This approach is convenient for relations.
+
#
# The available_queries_for_explain thread variable collects the queries
# to be explained. If the value is nil, it means queries are not being
@@ -21,7 +22,7 @@ module ActiveRecord
threshold = auto_explain_threshold_in_seconds
current = Thread.current
- if threshold && current[:available_queries_for_explain].nil?
+ if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil?
begin
queries = current[:available_queries_for_explain] = []
start = Time.now
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 7922bbcfa0..ea3bb8f33f 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -753,9 +753,8 @@ module ActiveRecord
def fixtures(*fixture_set_names)
if fixture_set_names.first == :all
- fixture_set_names = Dir["#{fixture_path}/**/*.yml"].map { |f|
- File.basename f, '.yml'
- }
+ fixture_set_names = Dir["#{fixture_path}/**/*.{yml}"]
+ fixture_set_names.map! { |f| f[(fixture_path.size + 1)..-5] }
else
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
@@ -872,11 +871,7 @@ module ActiveRecord
end
def teardown_fixtures
- return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
-
- unless run_in_transaction?
- ActiveRecord::FixtureSet.reset_cache
- end
+ return if ActiveRecord::Base.configurations.blank?
# Rollback changes if a transaction is active.
if run_in_transaction?
@@ -884,7 +879,10 @@ module ActiveRecord
connection.rollback_transaction if connection.transaction_open?
end
@fixture_connections.clear
+ else
+ ActiveRecord::FixtureSet.reset_cache
end
+
ActiveRecord::Base.clear_active_connections!
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index a448fa1f5c..6ab67fdece 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -9,6 +9,19 @@ module ActiveRecord
end
module ClassMethods
+ # Determines if one of the attributes passed in is the inheritance column,
+ # and if the inheritance column is attr accessible, it initializes an
+ # instance of the given subclass instead of the base class
+ def new(*args, &block)
+ if (attrs = args.first).is_a?(Hash)
+ if subclass = subclass_from_attrs(attrs)
+ return subclass.new(*args, &block)
+ end
+ end
+ # Delegate to the original .new
+ super
+ end
+
# True if this isn't a concrete subclass needing a STI type condition.
def descends_from_active_record?
if self == Base
@@ -79,15 +92,6 @@ module ActiveRecord
store_full_sti_class ? name : name.demodulize
end
- # Finder methods must instantiate through this method to work with the
- # single-table inheritance model that makes it possible to create
- # objects of different types from the same table.
- def instantiate(record, column_types = {})
- sti_class = find_sti_class(record[inheritance_column])
- column_types = sti_class.decorate_columns(column_types)
- sti_class.allocate.init_with('attributes' => record, 'column_types' => column_types)
- end
-
protected
# Returns the class type of the record using the current module as a prefix. So descendants of
@@ -119,24 +123,33 @@ module ActiveRecord
private
+ # Called by +instantiate+ to decide which class to use for a new
+ # record instance. For single-table inheritance, we check the record
+ # for a +type+ column and return the corresponding class.
+ def discriminate_class_for_record(record)
+ if using_single_table_inheritance?(record)
+ find_sti_class(record[inheritance_column])
+ else
+ super
+ end
+ end
+
+ def using_single_table_inheritance?(record)
+ record[inheritance_column].present? && columns_hash.include?(inheritance_column)
+ end
+
def find_sti_class(type_name)
- if type_name.blank? || !columns_hash.include?(inheritance_column)
- self
+ if store_full_sti_class
+ ActiveSupport::Dependencies.constantize(type_name)
else
- begin
- if store_full_sti_class
- ActiveSupport::Dependencies.constantize(type_name)
- else
- compute_type(type_name)
- end
- rescue NameError
- raise SubclassNotFound,
- "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
- "or overwrite #{name}.inheritance_column to use another column for that information."
- end
+ compute_type(type_name)
end
+ rescue NameError
+ raise SubclassNotFound,
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
+ "or overwrite #{name}.inheritance_column to use another column for that information."
end
def type_condition(table = arel_table)
@@ -145,6 +158,19 @@ module ActiveRecord
sti_column.in(sti_names)
end
+
+ # Detect the subclass from the inheritance column of attrs. If the inheritance column value
+ # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound
+ # If this is a StrongParameters hash, and access to inheritance_column is not permitted,
+ # this will ignore the inheritance column and return nil
+ def subclass_from_attrs(attrs)
+ subclass_name = attrs.with_indifferent_access[inheritance_column]
+ return nil if subclass_name.blank? || subclass_name == self.name
+ unless subclass = subclasses.detect { |sub| sub.name == subclass_name }
+ raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
+ end
+ subclass
+ end
end
private
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 23c272ef12..7f877a6471 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -1,5 +1,16 @@
module ActiveRecord
module Integration
+ extend ActiveSupport::Concern
+
+ included do
+ ##
+ # :singleton-method:
+ # Indicates the format used to generate the timestamp format in the cache key.
+ # This is +:number+, by default.
+ class_attribute :cache_timestamp_format, :instance_writer => false
+ self.cache_timestamp_format = :nsec
+ end
+
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -29,8 +40,6 @@ module ActiveRecord
# Returns a cache key that can be used to identify this record.
#
- # ==== Examples
- #
# Product.new.cache_key # => "products/new"
# Product.find(5).cache_key # => "products/5" (updated_at not available)
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
@@ -39,7 +48,7 @@ module ActiveRecord
when new_record?
"#{self.class.model_name.cache_key}/new"
when timestamp = self[:updated_at]
- timestamp = timestamp.utc.to_s(:nsec)
+ timestamp = timestamp.utc.to_s(cache_timestamp_format)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
"#{self.class.model_name.cache_key}/#{id}"
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 035c77c424..701949e57b 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -66,7 +66,7 @@ module ActiveRecord
send(lock_col + '=', previous_lock_value + 1)
end
- def update(attribute_names = @attributes.keys) #:nodoc:
+ def update_record(attribute_names = @attributes.keys) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index ca79950049..2366a91bb5 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -1,7 +1,7 @@
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
-
+
def self.runtime=(value)
Thread.current[:active_record_sql_runtime] = value
end
@@ -20,6 +20,16 @@ module ActiveRecord
@odd_or_even = false
end
+ def render_bind(column, value)
+ if column.type == :binary
+ rendered_value = "<#{value.bytesize} bytes of binary data>"
+ else
+ rendered_value = value
+ end
+
+ [column.name, rendered_value]
+ end
+
def sql(event)
self.class.runtime += event.duration
return unless logger.debug?
@@ -34,7 +44,7 @@ module ActiveRecord
unless (payload[:binds] || []).empty?
binds = " " + payload[:binds].map { |col,v|
- [col.name, v]
+ render_bind(col, v)
}.inspect
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 22347fcaef..67339c05e5 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -32,7 +32,7 @@ module ActiveRecord
class PendingMigrationError < ActiveRecordError#:nodoc:
def initialize
- super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{ENV['RAILS_ENV']}' to resolve this issue.")
+ super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.")
end
end
@@ -373,22 +373,129 @@ module ActiveRecord
@name = name
@version = version
@connection = nil
- @reverting = false
end
# instantiate the delegate object after initialize is defined
self.verbose = true
self.delegate = new
- def revert
- @reverting = true
- yield
- ensure
- @reverting = false
+ # Reverses the migration commands for the given block and
+ # the given migrations.
+ #
+ # The following migration will remove the table 'horses'
+ # and create the table 'apples' on the way up, and the reverse
+ # on the way down.
+ #
+ # class FixTLMigration < ActiveRecord::Migration
+ # def change
+ # revert do
+ # create_table(:horses) do |t|
+ # t.text :content
+ # t.datetime :remind_at
+ # end
+ # end
+ # create_table(:apples) do |t|
+ # t.string :variety
+ # end
+ # end
+ # end
+ #
+ # Or equivalently, if +TenderloveMigration+ is defined as in the
+ # documentation for Migration:
+ #
+ # require_relative '2012121212_tenderlove_migration'
+ #
+ # class FixupTLMigration < ActiveRecord::Migration
+ # def change
+ # revert TenderloveMigration
+ #
+ # create_table(:apples) do |t|
+ # t.string :variety
+ # end
+ # end
+ # end
+ #
+ # This command can be nested.
+ def revert(*migration_classes)
+ run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
+ if block_given?
+ if @connection.respond_to? :revert
+ @connection.revert { yield }
+ else
+ recorder = CommandRecorder.new(@connection)
+ @connection = recorder
+ suppress_messages do
+ @connection.revert { yield }
+ end
+ @connection = recorder.delegate
+ recorder.commands.each do |cmd, args, block|
+ send(cmd, *args, &block)
+ end
+ end
+ end
end
def reverting?
- @reverting
+ @connection.respond_to?(:reverting) && @connection.reverting
+ end
+
+ class ReversibleBlockHelper < Struct.new(:reverting)
+ def up
+ yield unless reverting
+ end
+
+ def down
+ yield if reverting
+ end
+ end
+
+ # Used to specify an operation that can be run in one direction or another.
+ # Call the methods +up+ and +down+ of the yielded object to run a block
+ # only in one given direction.
+ # The whole block will be called in the right order within the migration.
+ #
+ # In the following example, the looping on users will always be done
+ # when the three columns 'first_name', 'last_name' and 'full_name' exist,
+ # even when migrating down:
+ #
+ # class SplitNameMigration < ActiveRecord::Migration
+ # def change
+ # add_column :users, :first_name, :string
+ # add_column :users, :last_name, :string
+ #
+ # reversible do |dir|
+ # User.reset_column_information
+ # User.all.each do |u|
+ # dir.up { u.first_name, u.last_name = u.full_name.split(' ') }
+ # dir.down { u.full_name = "#{u.first_name} #{u.last_name}" }
+ # u.save
+ # end
+ # end
+ #
+ # revert { add_column :users, :full_name, :string }
+ # end
+ # end
+ def reversible
+ helper = ReversibleBlockHelper.new(reverting?)
+ execute_block{ yield helper }
+ end
+
+ # Runs the given migration classes.
+ # Last argument can specify options:
+ # - :direction (default is :up)
+ # - :revert (default is false)
+ def run(*migration_classes)
+ opts = migration_classes.extract_options!
+ dir = opts[:direction] || :up
+ dir = (dir == :down ? :up : :down) if opts[:revert]
+ if reverting?
+ # If in revert and going :up, say, we want to execute :down without reverting, so
+ revert { run(*migration_classes, direction: dir, revert: true) }
+ else
+ migration_classes.each do |migration_class|
+ migration_class.new.exec_migration(@connection, dir)
+ end
+ end
end
def up
@@ -414,29 +521,9 @@ module ActiveRecord
time = nil
ActiveRecord::Base.connection_pool.with_connection do |conn|
- @connection = conn
- if respond_to?(:change)
- if direction == :down
- recorder = CommandRecorder.new(@connection)
- suppress_messages do
- @connection = recorder
- change
- end
- @connection = conn
- time = Benchmark.measure {
- self.revert {
- recorder.inverse.each do |cmd, args|
- send(cmd, *args)
- end
- }
- }
- else
- time = Benchmark.measure { change }
- end
- else
- time = Benchmark.measure { send(direction) }
+ time = Benchmark.measure do
+ exec_migration(conn, direction)
end
- @connection = nil
end
case direction
@@ -445,6 +532,21 @@ module ActiveRecord
end
end
+ def exec_migration(conn, direction)
+ @connection = conn
+ if respond_to?(:change)
+ if direction == :down
+ revert { change }
+ else
+ change
+ end
+ else
+ send(direction)
+ end
+ ensure
+ @connection = nil
+ end
+
def write(text="")
puts(text) if verbose
end
@@ -483,7 +585,7 @@ module ActiveRecord
arg_list = arguments.map{ |a| a.inspect } * ', '
say_with_time "#{method}(#{arg_list})" do
- unless reverting?
+ unless @connection.respond_to? :revert
unless arguments.empty? || method == :execute
arguments[0] = Migrator.proper_table_name(arguments.first)
arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
@@ -537,6 +639,15 @@ module ActiveRecord
"%.3d" % number
end
end
+
+ private
+ def execute_block
+ if connection.respond_to? :execute_block
+ super # use normal delegation to record the block
+ else
+ yield
+ end
+ end
end
# MigrationProxy is used to defer loading of the actual migration classes
@@ -665,7 +776,7 @@ module ActiveRecord
files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
- version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/).first
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index 95f4360578..79c55045ba 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -16,69 +16,117 @@ module ActiveRecord
class CommandRecorder
include JoinTable
- attr_accessor :commands, :delegate
+ attr_accessor :commands, :delegate, :reverting
def initialize(delegate = nil)
@commands = []
@delegate = delegate
+ @reverting = false
+ end
+
+ # While executing the given block, the recorded will be in reverting mode.
+ # All commands recorded will end up being recorded reverted
+ # and in reverse order.
+ # For example:
+ #
+ # recorder.revert{ recorder.record(:rename_table, [:old, :new]) }
+ # # same effect as recorder.record(:rename_table, [:new, :old])
+ def revert
+ @reverting = !@reverting
+ previous = @commands
+ @commands = []
+ yield
+ ensure
+ @commands = previous.concat(@commands.reverse)
+ @reverting = !@reverting
end
# record +command+. +command+ should be a method name and arguments.
# For example:
#
# recorder.record(:method_name, [:arg1, :arg2])
- def record(*command)
- @commands << command
+ def record(*command, &block)
+ if @reverting
+ @commands << inverse_of(*command, &block)
+ else
+ @commands << (command << block)
+ end
end
- # Returns a list that represents commands that are the inverse of the
- # commands stored in +commands+. For example:
+ # Returns the inverse of the given command. For example:
#
- # recorder.record(:rename_table, [:old, :new])
- # recorder.inverse # => [:rename_table, [:new, :old]]
+ # recorder.inverse_of(:rename_table, [:old, :new])
+ # # => [:rename_table, [:new, :old]]
#
# This method will raise an +IrreversibleMigration+ exception if it cannot
- # invert the +commands+.
- def inverse
- @commands.reverse.map { |name, args|
- method = :"invert_#{name}"
- raise IrreversibleMigration unless respond_to?(method, true)
- send(method, args)
- }
+ # invert the +command+.
+ def inverse_of(command, args, &block)
+ method = :"invert_#{command}"
+ raise IrreversibleMigration unless respond_to?(method, true)
+ send(method, args, &block)
end
def respond_to?(*args) # :nodoc:
super || delegate.respond_to?(*args)
end
- [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default, :add_reference, :remove_reference].each do |method|
+ [:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
+ :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
+ :change_column_default, :add_reference, :remove_reference, :transaction,
+ :drop_join_table, :drop_table, :execute_block,
+ :change_column, :execute, :remove_columns, # irreversible methods need to be here too
+ ].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
- def #{method}(*args) # def create_table(*args)
- record(:"#{method}", args) # record(:create_table, args)
- end # end
+ def #{method}(*args, &block) # def create_table(*args, &block)
+ record(:"#{method}", args, &block) # record(:create_table, args, &block)
+ end # end
EOV
end
alias :add_belongs_to :add_reference
alias :remove_belongs_to :remove_reference
- private
-
- def invert_create_table(args)
- [:drop_table, [args.first]]
+ def change_table(table_name, options = {})
+ yield ConnectionAdapters::Table.new(table_name, self)
end
- def invert_create_join_table(args)
- table_name = find_join_table_name(*args)
+ private
- [:drop_table, [table_name]]
+ module StraightReversions
+ private
+ { transaction: :transaction,
+ execute_block: :execute_block,
+ create_table: :drop_table,
+ create_join_table: :drop_join_table,
+ add_column: :remove_column,
+ add_timestamps: :remove_timestamps,
+ add_reference: :remove_reference,
+ }.each do |cmd, inv|
+ [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
+ class_eval <<-EOV, __FILE__, __LINE__ + 1
+ def invert_#{method}(args, &block) # def invert_create_table(args, &block)
+ [:#{inverse}, args, block] # [:drop_table, args, block]
+ end # end
+ EOV
+ end
+ end
+ end
+
+ include StraightReversions
+
+ def invert_drop_table(args, &block)
+ if args.size == 1 && block == nil
+ raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
+ end
+ super
end
def invert_rename_table(args)
[:rename_table, args.reverse]
end
- def invert_add_column(args)
- [:remove_column, args.first(2)]
+ def invert_remove_column(args)
+ raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
+ super
end
def invert_rename_index(args)
@@ -91,27 +139,18 @@ module ActiveRecord
def invert_add_index(args)
table, columns, options = *args
- index_name = options.try(:[], :name)
- options_hash = index_name ? {:name => index_name} : {:column => columns}
- [:remove_index, [table, options_hash]]
+ [:remove_index, [table, (options || {}).merge(column: columns)]]
end
- def invert_remove_timestamps(args)
- [:add_timestamps, args]
- end
+ def invert_remove_index(args)
+ table, options = *args
+ raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." unless options && options[:column]
- def invert_add_timestamps(args)
- [:remove_timestamps, args]
+ options = options.dup
+ [:add_index, [table, options.delete(:column), options]]
end
- def invert_add_reference(args)
- [:remove_reference, args]
- end
alias :invert_add_belongs_to :invert_add_reference
-
- def invert_remove_reference(args)
- [:add_reference, args]
- end
alias :invert_remove_belongs_to :invert_remove_reference
# Forwards any missing method call to the \target.
diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb
index e880ae97bb..ebf64cbcdc 100644
--- a/activerecord/lib/active_record/migration/join_table.rb
+++ b/activerecord/lib/active_record/migration/join_table.rb
@@ -8,7 +8,7 @@ module ActiveRecord
end
def join_table_name(table_1, table_2)
- [table_1, table_2].sort.join("_").to_sym
+ [table_1.to_s, table_2.to_s].sort.join("_").to_sym
end
end
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 628ab0f566..85fb4be992 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -224,11 +224,10 @@ module ActiveRecord
def decorate_columns(columns_hash) # :nodoc:
return if columns_hash.empty?
- serialized_attributes.each_key do |key|
- columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key])
- end
-
columns_hash.each do |name, col|
+ if serialized_attributes.key?(name)
+ columns_hash[name] = AttributeMethods::Serialization::Type.new(col)
+ end
if create_time_zone_conversion_attribute?(name, col)
columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col)
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 4c9bd76d7c..c5bd11edbf 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -58,7 +58,7 @@ module ActiveRecord
# It also allows you to update the avatar through the member:
#
# params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
- # member.update_attributes params[:member]
+ # member.update params[:member]
# member.avatar.icon # => 'sad'
#
# By default you will only be able to set and update attributes on the
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
deleted file mode 100644
index 6b2f6f98a5..0000000000
--- a/activerecord/lib/active_record/observer.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-
-module ActiveRecord
- # = Active Record Observer
- #
- # Observer classes respond to life cycle callbacks to implement trigger-like
- # behavior outside the original class. This is a great way to reduce the
- # clutter that normally comes when the model class is burdened with
- # functionality that doesn't pertain to the core responsibility of the
- # class. Example:
- #
- # class CommentObserver < ActiveRecord::Observer
- # def after_save(comment)
- # Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
- # end
- # end
- #
- # This Observer sends an email when a Comment#save is finished.
- #
- # class ContactObserver < ActiveRecord::Observer
- # def after_create(contact)
- # contact.logger.info('New contact added!')
- # end
- #
- # def after_destroy(contact)
- # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
- # end
- # end
- #
- # This Observer uses logger to log when specific callbacks are triggered.
- #
- # == Observing a class that can't be inferred
- #
- # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
- # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
- # differently than the class you're interested in observing, you can use the Observer.observe class method which takes
- # either the concrete class (Product) or a symbol for that class (:product):
- #
- # class AuditObserver < ActiveRecord::Observer
- # observe :account
- #
- # def after_update(account)
- # AuditTrail.new(account, "UPDATED")
- # end
- # end
- #
- # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
- #
- # class AuditObserver < ActiveRecord::Observer
- # observe :account, :balance
- #
- # def after_update(record)
- # AuditTrail.new(record, "UPDATED")
- # end
- # end
- #
- # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
- #
- # == Available callback methods
- #
- # The observer can implement callback methods for each of the methods described in the Callbacks module.
- #
- # == Storing Observers in Rails
- #
- # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
- # naming convention of app/models/audit_observer.rb.
- #
- # == Configuration
- #
- # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration
- # setting in your <tt>config/application.rb</tt> file.
- #
- # config.active_record.observers = :comment_observer, :signup_observer
- #
- # Observers will not be invoked unless you define these in your application configuration.
- #
- # If you are using Active Record outside Rails, activate the observers explicitly in a configuration or
- # environment file:
- #
- # ActiveRecord::Base.add_observer CommentObserver.instance
- # ActiveRecord::Base.add_observer SignupObserver.instance
- #
- # == Loading
- #
- # Observers register themselves in the model class they observe, since it is the class that
- # notifies them of events when they occur. As a side-effect, when an observer is loaded its
- # corresponding model class is loaded.
- #
- # Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
- # application initializers. Now observers are loaded after application initializers,
- # so observed models can make use of extensions.
- #
- # If by any chance you are using observed models in the initialization you can still
- # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
- # singletons and that call instantiates and registers them.
- #
- class Observer < ActiveModel::Observer
-
- protected
-
- def observed_classes
- klasses = super
- klasses + klasses.map { |klass| klass.descendants }.flatten
- end
-
- def add_observer!(klass)
- super
- define_callbacks klass
- end
-
- def define_callbacks(klass)
- observer = self
- observer_name = observer.class.name.underscore.gsub('/', '__')
-
- ActiveRecord::Callbacks::CALLBACKS.each do |callback|
- next unless respond_to?(callback)
- callback_meth = :"_notify_#{observer_name}_for_#{callback}"
- unless klass.respond_to?(callback_meth)
- klass.send(:define_method, callback_meth) do |&block|
- observer.update(callback, self, &block)
- end
- klass.send(callback, callback_meth)
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 8e749772a1..3011f959a5 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -38,6 +38,32 @@ module ActiveRecord
object
end
end
+
+ # Given an attributes hash, +instantiate+ returns a new instance of
+ # the appropriate class.
+ #
+ # For example, +Post.all+ may return Comments, Messages, and Emails
+ # by storing the record's subclass in a +type+ attribute. By calling
+ # +instantiate+ instead of +new+, finder methods ensure they get new
+ # instances of the appropriate class for each record.
+ #
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
+ # how this "single-table" inheritance mapping is implemented.
+ def instantiate(record, column_types = {})
+ klass = discriminate_class_for_record(record)
+ column_types = klass.decorate_columns(column_types)
+ klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
+ end
+
+ private
+ # Called by +instantiate+ to decide which class to use for a new
+ # record instance.
+ #
+ # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for
+ # the single-table inheritance discriminator.
+ def discriminate_class_for_record(record)
+ self
+ end
end
# Returns true if this object hasn't been saved yet -- that is, a record
@@ -72,11 +98,9 @@ module ActiveRecord
# +save+ returns +false+. See ActiveRecord::Callbacks for further
# details.
def save(*)
- begin
- create_or_update
- rescue ActiveRecord::RecordInvalid
- false
- end
+ create_or_update
+ rescue ActiveRecord::RecordInvalid
+ false
end
# Saves the model.
@@ -104,7 +128,7 @@ module ActiveRecord
# record's primary key, and no callbacks are executed.
#
# To enforce the object's +before_destroy+ and +after_destroy+
- # callbacks, Observer methods, or any <tt>:dependent</tt> association
+ # callbacks or any <tt>:dependent</tt> association
# options, use <tt>#destroy</tt>.
def delete
self.class.delete(id) if persisted?
@@ -155,7 +179,18 @@ module ActiveRecord
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
became.instance_variable_set("@errors", errors)
- became.public_send("#{klass.inheritance_column}=", klass.name) unless self.class.descends_from_active_record?
+ became
+ end
+
+ # Wrapper around +becomes+ that also changes the instance's sti column value.
+ # This is especially useful if you want to persist the changed class in your
+ # database.
+ #
+ # Note: The old instance's sti column value will be changed too, as both objects
+ # share the same set of attributes.
+ def becomes!(klass)
+ became = becomes(klass)
+ became.public_send("#{klass.inheritance_column}=", klass.sti_name) unless self.class.descends_from_active_record?
became
end
@@ -171,13 +206,13 @@ module ActiveRecord
name = name.to_s
verify_readonly_attribute(name)
send("#{name}=", value)
- save(:validate => false)
+ save(validate: false)
end
# Updates the attributes of the model from the passed-in hash and saves the
# record, all wrapped in a transaction. If the object is invalid, the saving
# will fail and false will be returned.
- def update_attributes(attributes)
+ def update(attributes)
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
@@ -185,10 +220,12 @@ module ActiveRecord
save
end
end
+
+ alias update_attributes update
- # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
+ # Updates its receiver just like +update+ but calls <tt>save!</tt> instead
# of +save+, so an exception is raised if the record is invalid.
- def update_attributes!(attributes)
+ def update!(attributes)
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
@@ -196,6 +233,8 @@ module ActiveRecord
save!
end
end
+
+ alias update_attributes! update!
# Updates a single attribute of an object, without having to explicitly call save on that object.
#
@@ -224,10 +263,10 @@ module ActiveRecord
verify_readonly_attribute(key.to_s)
end
- updated_count = self.class.where(self.class.primary_key => id).update_all(attributes)
+ updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
- attributes.each do |k,v|
- raw_write_attribute(k,v)
+ attributes.each do |k, v|
+ raw_write_attribute(k, v)
end
updated_count == 1
@@ -371,23 +410,27 @@ module ActiveRecord
def create_or_update
raise ReadOnlyRecord if readonly?
- result = new_record? ? create : update
+ result = new_record? ? create_record : update_record
result != false
end
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
- def update(attribute_names = @attributes.keys)
+ def update_record(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
- return 0 if attributes_with_values.empty?
- klass = self.class
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
- klass.connection.update stmt
+
+ if attributes_with_values.empty?
+ 0
+ else
+ klass = self.class
+ stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
+ klass.connection.update stmt
+ end
end
# Creates a record with values matching those of the instance attributes
# and returns its id.
- def create(attribute_names = @attributes.keys)
+ def create_record(attribute_names = @attributes.keys)
attributes_values = arel_attributes_with_values_for_create(attribute_names)
new_id = self.class.unscoped.insert attributes_values
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 38e18b32a4..df8654e5c1 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -4,6 +4,7 @@ module ActiveRecord
class QueryCache
module ClassMethods
# Enable the query cache within the block if Active Record is configured.
+ # If it's not, it will execute the given block.
def cache(&block)
if ActiveRecord::Base.connected?
connection.cache(&block)
@@ -13,6 +14,7 @@ module ActiveRecord
end
# Disable the query cache within the block if Active Record is configured.
+ # If it's not, it will execute the given block.
def uncached(&block)
if ActiveRecord::Base.connected?
connection.uncached(&block)
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 45f6a78428..5ddcaee6be 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -26,14 +26,13 @@ module ActiveRecord
# MySQL specific terms will lock you to using that particular database engine or require you to
# change your call if you switch engines.
#
- # ==== Examples
# # A simple SQL query spanning multiple tables
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
- # > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
+ # # => [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
#
# # You can use the same string replacement techniques as you can with ActiveRecord#find
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
- # > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
+ # # => [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
def find_by_sql(sql, binds = [])
logging_query_plan do
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
@@ -57,8 +56,6 @@ module ActiveRecord
#
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
#
- # ==== Examples
- #
# Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
def count_by_sql(sql)
logging_query_plan do
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 5464ca6066..aceb70bc45 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -112,7 +112,19 @@ module ActiveRecord
`config/application.rb` file and any `mass_assignment_sanitizer` options
from your `config/environments/*.rb` files.
- See http://edgeguides.rubyonrails.org/security.html#mass-assignment for more information
+ See http://guides.rubyonrails.org/security.html#mass-assignment for more information
+ EOF
+ end
+
+ unless app.config.active_record.delete(:observers).nil?
+ ActiveSupport::Deprecation.warn <<-EOF.strip_heredoc, []
+ Active Record Observers has been extracted out of Rails into a gem.
+ Please use callbacks or add `rails-observers` to your Gemfile to use observers.
+
+ To disable this message remove the `observers` option from your
+ `config/application.rb` or from your initializers.
+
+ See http://guides.rubyonrails.org/4_0_release_notes.html for more information
EOF
end
ensure
@@ -136,6 +148,13 @@ module ActiveRecord
end
end
+ initializer "active_record.validate_explain_support" do |app|
+ if app.config.active_record[:auto_explain_threshold_in_seconds] &&
+ !ActiveRecord::Base.connection.supports_explain?
+ warn "auto_explain_threshold_in_seconds is set but will be ignored because your adapter does not support this feature. Please unset the configuration to avoid this warning."
+ end
+ end
+
# Expose database runtime to controller for logging.
initializer "active_record.log_runtime" do |app|
require "active_record/railties/controller_runtime"
@@ -161,15 +180,5 @@ module ActiveRecord
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
end
-
- config.after_initialize do |app|
- ActiveSupport.on_load(:active_record) do
- instantiate_observers
-
- ActionDispatch::Reloader.to_prepare do
- ActiveRecord::Base.instantiate_observers
- end
- end
- end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 0a9caa25b2..259d0ff12b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -167,7 +167,7 @@ db_namespace = namespace :db do
# desc "Raises an error if there are pending migrations"
task :abort_if_pending_migrations => [:environment, :load_config] do
- pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
+ pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
puts "You have #{pending_migrations.size} pending migrations:"
@@ -300,7 +300,9 @@ db_namespace = namespace :db do
end
if ActiveRecord::Base.connection.supports_migrations?
- File.open(filename, "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
+ File.open(filename, "a") do |f|
+ f.puts ActiveRecord::Base.connection.dump_schema_information
+ end
end
db_namespace['structure:dump'].reenable
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0103de4cbd..bcfcb061f2 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -179,7 +179,7 @@ module ActiveRecord
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
end
- # Returns a new, unsaved instance of the associated class. +options+ will
+ # Returns a new, unsaved instance of the associated class. +attributes+ will
# be passed to the class's constructor.
def build_association(attributes, &block)
klass.new(attributes, &block)
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 3ee55c580e..6ec5cf3e18 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -31,6 +31,14 @@ module ActiveRecord
@default_scoped = false
end
+ def initialize_copy(other)
+ # This method is a hot spot, so for now, use Hash[] to dup the hash.
+ # https://bugs.ruby-lang.org/issues/7166
+ @values = Hash[@values]
+ @values[:bind] = @values[:bind].dup if @values.key? :bind
+ reset
+ end
+
def insert(values)
primary_key_value = nil
@@ -90,14 +98,6 @@ module ActiveRecord
scoping { @klass.new(*args, &block) }
end
- def initialize_copy(other)
- # This method is a hot spot, so for now, use Hash[] to dup the hash.
- # https://bugs.ruby-lang.org/issues/7166
- @values = Hash[@values]
- @values[:bind] = @values[:bind].dup if @values.key? :bind
- reset
- end
-
alias build new
# Tries to create a new record with the same scoped attributes
@@ -308,18 +308,16 @@ module ActiveRecord
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
else
object = find(id)
- object.update_attributes(attributes)
+ object.update(attributes)
object
end
end
# Destroys the records matching +conditions+ by instantiating each
# record and calling its +destroy+ method. Each object's callbacks are
- # executed (including <tt>:dependent</tt> association options and
- # +before_destroy+/+after_destroy+ Observer methods). Returns the
+ # executed (including <tt>:dependent</tt> association options). Returns the
# collection of objects that were destroyed; each will be frozen, to
- # reflect that no changes should be made (since they can't be
- # persisted).
+ # reflect that no changes should be made (since they can't be persisted).
#
# Note: Instantiation, callback execution, and deletion of each
# record can be time consuming when you're removing many records at
@@ -419,8 +417,7 @@ module ActiveRecord
# Deletes the row with a primary key matching the +id+ argument, using a
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
# Record objects are not instantiated, so the object's callbacks are not
- # executed, including any <tt>:dependent</tt> association options or
- # Observer methods.
+ # executed, including any <tt>:dependent</tt> association options.
#
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
#
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 741f94f777..0b27ea730b 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation'
+
module ActiveRecord
module Calculations
# Count the records.
@@ -13,16 +15,9 @@ 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 = {})
- 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
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
+ calculate(:count, column_name, options)
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -56,13 +51,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) { |item| yield item }
+ ActiveSupport::Deprecation.warn(
+ "Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \
+ "If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`."
+ )
+ self.to_a.sum(*args) {|*block_args| yield(*block_args)}
else
calculate(:sum, *args)
end
@@ -81,18 +76,17 @@ module ActiveRecord
#
# values = Person.group('last_name').maximum(:age)
# puts values["Drake"]
- # => 43
+ # # => 43
#
# drake = Family.find_by_last_name('Drake')
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
- # => 43
+ # # => 43
#
# values.each do |family, max_age|
# ...
# end
#
- # Examples:
# Person.calculate(:count, :all) # The same as Person.count
# Person.average(:age) # SELECT AVG(age) FROM people...
#
@@ -116,8 +110,8 @@ module ActiveRecord
0
end
- # Use <tt>pluck</tt> as a shortcut to select a single attribute without
- # loading a bunch of records just to grab one attribute you want.
+ # Use <tt>pluck</tt> as a shortcut to select one or more attributes without
+ # loading a bunch of records just to grab the attributes you want.
#
# Person.pluck(:name)
#
@@ -126,11 +120,9 @@ 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 an SQL fragment
+ # the plucked column names, if they can be deduced. Plucking an SQL fragment
# returns String values by default.
#
- # Examples:
- #
# Person.pluck(:id)
# # SELECT people.id FROM people
# # => [1, 2, 3]
@@ -187,8 +179,6 @@ module ActiveRecord
# Pluck all the ID's for the relation using the table's primary key
#
- # Examples:
- #
# Person.ids # SELECT people.id FROM people
# Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
def ids
@@ -241,6 +231,8 @@ module ActiveRecord
# Postgresql doesn't like ORDER BY when there are no GROUP BY
relation = reorder(nil)
+ column_alias = column_name
+
if operation == "count" && (relation.limit_value || relation.offset_value)
# Shortcut when limit is zero.
return 0 if relation.limit_value == 0
@@ -251,13 +243,20 @@ module ActiveRecord
select_value = operation_over_aggregate_column(column, operation, distinct)
+ column_alias = select_value.alias
relation.select_values = [select_value]
query_builder = relation.arel
end
- result = @klass.connection.select_value(query_builder, nil, relation.bind_values)
- type_cast_calculated_value(result, column_for(column_name), operation)
+ result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
+ row = result.first
+ value = row && row.values.first
+ column = result.column_types.fetch(column_alias) do
+ column_for(column_name)
+ end
+
+ type_cast_calculated_value(value, column, operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
@@ -275,7 +274,7 @@ module ActiveRecord
column_alias_for(field)
}
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
- [aliaz, column_for(field)]
+ [aliaz, field]
}
group = group_fields
@@ -315,7 +314,10 @@ module ActiveRecord
end
Hash[calculated_data.map do |row|
- key = group_columns.map { |aliaz, column|
+ key = group_columns.map { |aliaz, col_name|
+ column = calculated_data.column_types.fetch(aliaz) do
+ column_for(col_name)
+ end
type_cast_calculated_value(row[aliaz], column)
}
key = key.first if key.size == 1
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index dbfa92bbbd..431d083f21 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,27 +1,107 @@
+require 'active_support/concern'
require 'thread'
+require 'thread_safe'
module ActiveRecord
module Delegation # :nodoc:
- # Set up common delegations for performance (avoids method_missing)
+ extend ActiveSupport::Concern
+
+ # This module creates compiled delegation methods dynamically at runtime, which makes
+ # subsequent calls to that method faster by avoiding method_missing. The delegations
+ # may vary depending on the klass of a relation, so we create a subclass of Relation
+ # for each different klass, and the delegations are compiled into that subclass only.
+
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
- @@delegation_mutex = Mutex.new
+ module ClassSpecificRelation
+ extend ActiveSupport::Concern
- def self.delegate_to_scoped_klass(method)
- if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method}(*args, &block)
- scoping { @klass.#{method}(*args, &block) }
+ included do
+ @delegation_mutex = Mutex.new
+ end
+
+ module ClassMethods
+ def name
+ superclass.name
+ end
+
+ def delegate_to_scoped_klass(method)
+ @delegation_mutex.synchronize do
+ return if method_defined?(method)
+
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(*args, &block)
+ scoping { @klass.#{method}(*args, &block) }
+ end
+ RUBY
+ else
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(*args, &block)
+ scoping { @klass.send(#{method.inspect}, *args, &block) }
+ end
+ RUBY
+ end
end
- RUBY
- else
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method}(*args, &block)
- scoping { @klass.send(#{method.inspect}, *args, &block) }
+ end
+
+ def delegate(method, opts = {})
+ @delegation_mutex.synchronize do
+ return if method_defined?(method)
+ super
+ end
+ end
+ end
+
+ protected
+
+ def method_missing(method, *args, &block)
+ if @klass.respond_to?(method)
+ self.class.delegate_to_scoped_klass(method)
+ scoping { @klass.send(method, *args, &block) }
+ elsif Array.method_defined?(method)
+ self.class.delegate method, :to => :to_a
+ to_a.send(method, *args, &block)
+ elsif arel.respond_to?(method)
+ self.class.delegate method, :to => :arel
+ arel.send(method, *args, &block)
+ else
+ super
+ end
+ end
+ end
+
+ module ClassMethods
+ @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
+
+ def new(klass, *args)
+ relation = relation_class_for(klass).allocate
+ relation.__send__(:initialize, klass, *args)
+ relation
+ end
+
+ # This doesn't have to be thread-safe. relation_class_for guarantees that this will only be
+ # called exactly once for a given const name.
+ def const_missing(name)
+ const_set(name, Class.new(self) { include ClassSpecificRelation })
+ end
+
+ private
+ # Cache the constants in @@subclasses because looking them up via const_get
+ # make instantiation significantly slower.
+ def relation_class_for(klass)
+ if klass && (klass_name = klass.name)
+ my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new }
+ # This hash is keyed by klass.name to avoid memory leaks in development mode
+ my_cache.compute_if_absent(klass_name) do
+ # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name
+ const_get("#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}", false)
end
- RUBY
+ else
+ ActiveRecord::Relation
+ end
end
end
@@ -35,28 +115,10 @@ module ActiveRecord
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
- @@delegation_mutex.synchronize do
- unless ::ActiveRecord::Delegation.method_defined?(method)
- ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
- end
- end
-
scoping { @klass.send(method, *args, &block) }
elsif Array.method_defined?(method)
- @@delegation_mutex.synchronize do
- unless ::ActiveRecord::Delegation.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
- end
- end
-
to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
- @@delegation_mutex.synchronize do
- unless ::ActiveRecord::Delegation.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :arel
- end
- end
-
arel.send(method, *args, &block)
else
super
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index eafe4a54c4..7ddaea1bb0 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -225,9 +225,11 @@ module ActiveRecord
orders = relation.order_values.map { |val| val.presence }.compact
values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
- relation = relation.dup
+ relation = relation.dup.select(values)
+
+ id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
+ ids_array = id_rows.map {|row| row[primary_key]}
- ids_array = relation.select(values).collect {|row| row[primary_key]}
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index b3712b4ad6..46c0d6206f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -4,6 +4,51 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
+ # WhereChain objects act as placeholder for queries in which #where does not have any parameter.
+ # In this case, #where must be chained with either #not, #like, or #not_like to return a new relation.
+ class WhereChain
+ def initialize(scope)
+ @scope = scope
+ end
+
+ # Returns a new relation expressing WHERE + NOT condition
+ # according to the conditions in the arguments.
+ #
+ # #not accepts conditions in one of these formats: String, Array, Hash.
+ # See #where for more details on each format.
+ #
+ # User.where.not("name = 'Jon'")
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
+ #
+ # User.where.not(["name = ?", "Jon"])
+ # # SELECT * FROM users WHERE NOT (name = 'Jon')
+ #
+ # User.where.not(name: "Jon")
+ # # SELECT * FROM users WHERE name != 'Jon'
+ #
+ # User.where.not(name: nil)
+ # # SELECT * FROM users WHERE name IS NOT NULL
+ #
+ # User.where.not(name: %w(Ko1 Nobu))
+ # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
+ def not(opts, *rest)
+ where_value = @scope.send(:build_where, opts, rest).map do |rel|
+ case rel
+ when Arel::Nodes::In
+ Arel::Nodes::NotIn.new(rel.left, rel.right)
+ when Arel::Nodes::Equality
+ Arel::Nodes::NotEqual.new(rel.left, rel.right)
+ when String
+ Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
+ else
+ Arel::Nodes::Not.new(rel)
+ end
+ end
+ @scope.where_values += where_value
+ @scope
+ end
+ end
+
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}_values # def select_values
@@ -66,8 +111,7 @@ module ActiveRecord
args.empty? ? self : spawn.includes!(*args)
end
- # Like #includes, but modifies the relation in place.
- def includes!(*args)
+ def includes!(*args) # :nodoc:
args.reject! {|a| a.blank? }
self.includes_values = (includes_values + args).flatten.uniq
@@ -84,8 +128,7 @@ module ActiveRecord
args.blank? ? self : spawn.eager_load!(*args)
end
- # Like #eager_load, but modifies relation in place.
- def eager_load!(*args)
+ def eager_load!(*args) # :nodoc:
self.eager_load_values += args
self
end
@@ -98,8 +141,7 @@ module ActiveRecord
args.blank? ? self : spawn.preload!(*args)
end
- # Like #preload, but modifies relation in place.
- def preload!(*args)
+ def preload!(*args) # :nodoc:
self.preload_values += args
self
end
@@ -116,8 +158,7 @@ module ActiveRecord
args.blank? ? self : spawn.references!(*args)
end
- # Like #references, but modifies relation in place.
- def references!(*args)
+ def references!(*args) # :nodoc:
args.flatten!
self.references_values = (references_values + args.map!(&:to_s)).uniq
@@ -162,8 +203,7 @@ module ActiveRecord
end
end
- # Like #select, but modifies relation in place.
- def select!(*fields)
+ def select!(*fields) # :nodoc:
self.select_values += fields.flatten
self
end
@@ -184,8 +224,7 @@ module ActiveRecord
args.blank? ? self : spawn.group!(*args)
end
- # Like #group, but modifies relation in place.
- def group!(*args)
+ def group!(*args) # :nodoc:
args.flatten!
self.group_values += args
@@ -215,8 +254,7 @@ module ActiveRecord
args.blank? ? self : spawn.order!(*args)
end
- # Like #order, but modifies relation in place.
- def order!(*args)
+ def order!(*args) # :nodoc:
args.flatten!
validate_order_args args
@@ -241,8 +279,7 @@ module ActiveRecord
args.blank? ? self : spawn.reorder!(*args)
end
- # Like #reorder, but modifies relation in place.
- def reorder!(*args)
+ def reorder!(*args) # :nodoc:
args.flatten!
validate_order_args args
@@ -259,8 +296,7 @@ module ActiveRecord
args.compact.blank? ? self : spawn.joins!(*args.flatten)
end
- # Like #joins, but modifies relation in place.
- def joins!(*args)
+ def joins!(*args) # :nodoc:
self.joins_values += args
self
end
@@ -269,7 +305,7 @@ module ActiveRecord
spawn.bind!(value)
end
- def bind!(value)
+ def bind!(value) # :nodoc:
self.bind_values += [value]
self
end
@@ -379,20 +415,41 @@ module ActiveRecord
# User.joins(:posts).where({ "posts.published" => true })
# User.joins(:posts).where({ posts: { published: true } })
#
- # === empty condition
+ # === no argument
+ #
+ # If no argument is passed, #where returns a new instance of WhereChain, that
+ # can be chained with #not to return a new relation that negates the where clause.
+ #
+ # User.where.not(name: "Jon")
+ # # SELECT * FROM users WHERE name != 'Jon'
+ #
+ # See WhereChain for more details on #not.
+ #
+ # === blank 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)
+ # If the condition is any blank-ish object, then #where is a no-op and returns
+ # the current relation.
+ def where(opts = :chain, *rest)
+ if opts == :chain
+ WhereChain.new(spawn)
+ elsif opts.blank?
+ self
+ else
+ spawn.where!(opts, *rest)
+ end
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
+ def where!(opts = :chain, *rest) # :nodoc:
+ if opts == :chain
+ WhereChain.new(self)
+ else
+ references!(PredicateBuilder.references(opts)) if Hash === opts
- self.where_values += build_where(opts, rest)
- self
+ self.where_values += build_where(opts, rest)
+ self
+ end
end
# Allows to specify a HAVING clause. Note that you can't use HAVING
@@ -403,8 +460,7 @@ module ActiveRecord
opts.blank? ? self : spawn.having!(opts, *rest)
end
- # Like #having, but modifies relation in place.
- def having!(opts, *rest)
+ def having!(opts, *rest) # :nodoc:
references!(PredicateBuilder.references(opts)) if Hash === opts
self.having_values += build_where(opts, rest)
@@ -420,8 +476,7 @@ module ActiveRecord
spawn.limit!(value)
end
- # Like #limit, but modifies relation in place.
- def limit!(value)
+ def limit!(value) # :nodoc:
self.limit_value = value
self
end
@@ -437,8 +492,7 @@ module ActiveRecord
spawn.offset!(value)
end
- # Like #offset, but modifies relation in place.
- def offset!(value)
+ def offset!(value) # :nodoc:
self.offset_value = value
self
end
@@ -449,8 +503,7 @@ module ActiveRecord
spawn.lock!(locks)
end
- # Like #lock, but modifies relation in place.
- def lock!(locks = true)
+ def lock!(locks = true) # :nodoc:
case locks
when String, TrueClass, NilClass
self.lock_value = locks || true
@@ -494,8 +547,7 @@ module ActiveRecord
extending(NullRelation)
end
- # Like #none, but modifies relation in place.
- def none!
+ def none! # :nodoc:
extending!(NullRelation)
end
@@ -509,8 +561,7 @@ module ActiveRecord
spawn.readonly!(value)
end
- # Like #readonly, but modifies relation in place.
- def readonly!(value = true)
+ def readonly!(value = true) # :nodoc:
self.readonly_value = value
self
end
@@ -532,12 +583,7 @@ module ActiveRecord
spawn.create_with!(value)
end
- # Like #create_with but modifies the relation in place. Raises
- # +ImmutableRelation+ if the relation has already been loaded.
- #
- # users = User.all.create_with!(name: 'Oscar')
- # users.new.name # => 'Oscar'
- def create_with!(value)
+ def create_with!(value) # :nodoc:
self.create_with_value = value ? create_with_value.merge(value) : {}
self
end
@@ -560,7 +606,7 @@ module ActiveRecord
end
# Like #from, but modifies relation in place.
- def from!(value, subquery_name = nil)
+ def from!(value, subquery_name = nil) # :nodoc:
self.from_value = [value, subquery_name]
self
end
@@ -580,7 +626,7 @@ module ActiveRecord
end
# Like #uniq, but modifies relation in place.
- def uniq!(value = true)
+ def uniq!(value = true) # :nodoc:
self.uniq_value = value
self
end
@@ -629,8 +675,7 @@ module ActiveRecord
end
end
- # Like #extending, but modifies relation in place.
- def extending!(*modules, &block)
+ def extending!(*modules, &block) # :nodoc:
modules << Module.new(&block) if block_given?
self.extending_values += modules.flatten
@@ -646,8 +691,7 @@ module ActiveRecord
spawn.reverse_order!
end
- # Like #reverse_order, but modifies relation in place.
- def reverse_order!
+ def reverse_order! # :nodoc:
self.reverse_order_value = !reverse_order_value
self
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 62dda542ab..de784f9f57 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -12,9 +12,6 @@ module ActiveRecord
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
- #
- # ==== Examples
- #
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
# # Performs a single join query with both where conditions.
#
@@ -29,7 +26,6 @@ module ActiveRecord
# # => Post.where(published: true).joins(:comments)
#
# This is mainly intended for sharing common conditions between multiple associations.
- #
def merge(other)
if other.is_a?(Array)
to_a & other
@@ -40,8 +36,7 @@ module ActiveRecord
end
end
- # Like #merge, but applies changes in place.
- def merge!(other)
+ def merge!(other) # :nodoc:
if !other.is_a?(Relation) && other.respond_to?(:to_proc)
instance_exec(&other)
else
@@ -52,31 +47,27 @@ module ActiveRecord
# Removes from the query the condition(s) specified in +skips+.
#
- # Example:
- #
# Post.order('id asc').except(:order) # discards the order condition
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
- #
def except(*skips)
- result = Relation.new(klass, table, values.except(*skips))
- result.default_scoped = default_scoped
- result.extend(*extending_values) if extending_values.any?
- result
+ relation_with values.except(*skips)
end
# Removes any condition from the query other than the one(s) specified in +onlies+.
#
- # Example:
- #
# Post.order('id asc').only(:where) # discards the order condition
# Post.order('id asc').only(:where, :order) # uses the specified order
- #
def only(*onlies)
- result = Relation.new(klass, table, values.slice(*onlies))
- result.default_scoped = default_scoped
- result.extend(*extending_values) if extending_values.any?
- result
+ relation_with values.slice(*onlies)
end
+ private
+
+ def relation_with(values) # :nodoc:
+ result = Relation.new(klass, table, values)
+ result.default_scoped = default_scoped
+ result.extend(*extending_values) if extending_values.any?
+ result
+ end
end
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index eaa4aa7086..3259dbbd80 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -29,11 +29,16 @@ module ActiveRecord
# ActiveRecord::Schema is only supported by database adapters that also
# support migrations, the two features being very similar.
class Schema < Migration
+
+ # Returns the migrations paths.
+ #
+ # ActiveRecord::Schema.new.migrations_paths
+ # # => ["db/migrate"] # Rails migration path by default.
def migrations_paths
ActiveRecord::Migrator.migrations_paths
end
- def define(info, &block)
+ def define(info, &block) # :nodoc:
instance_eval(&block)
unless info[:version].blank?
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index fb5f5b5be0..8b7eda6eee 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -116,7 +116,7 @@ module ActiveRecord
# Scopes can also be used while creating/building a record.
#
# class Article < ActiveRecord::Base
- # scope :published, -> { where(published: true) }
+ # scope :published, -> { where(published: true) }
# end
#
# Article.published.new.published # => true
@@ -126,7 +126,7 @@ module ActiveRecord
# on scopes. Assuming the following setup:
#
# class Article < ActiveRecord::Base
- # scope :published, -> { where(published: true) }
+ # scope :published, -> { where(published: true) }
# scope :featured, -> { where(featured: true) }
#
# def self.latest_article
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index cf17b1d8a4..8ded6d4a86 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -43,7 +43,7 @@ module ActiveRecord
private
- def create
+ def create_record
if self.record_timestamps
current_time = current_time_from_proper_timezone
@@ -57,7 +57,7 @@ module ActiveRecord
super
end
- def update(*args)
+ def update_record(*args)
if should_record_timestamps?
current_time = current_time_from_proper_timezone
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index ce6998530f..4a608e4f7b 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -4,6 +4,7 @@ module ActiveRecord
# See ActiveRecord::Transactions::ClassMethods for documentation.
module Transactions
extend ActiveSupport::Concern
+ ACTIONS = [:create, :destroy, :update]
class TransactionError < ActiveRecordError # :nodoc:
end
@@ -224,11 +225,7 @@ module ActiveRecord
# Note that transactional fixtures do not play well with this feature. Please
# use the +test_after_commit+ gem to have these hooks fired in tests.
def after_commit(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options[:on]
- options[:if] = Array(options[:if])
- options[:if] << "transaction_include_action?(:#{options[:on]})"
- end
+ set_options_for_callbacks!(args)
set_callback(:commit, :after, *args, &block)
end
@@ -236,12 +233,25 @@ module ActiveRecord
#
# Please check the documentation of +after_commit+ for options.
def after_rollback(*args, &block)
+ set_options_for_callbacks!(args)
+ set_callback(:rollback, :after, *args, &block)
+ end
+
+ private
+
+ def set_options_for_callbacks!(args)
options = args.last
if options.is_a?(Hash) && options[:on]
+ assert_valid_transaction_action(options[:on])
options[:if] = Array(options[:if])
options[:if] << "transaction_include_action?(:#{options[:on]})"
end
- set_callback(:rollback, :after, *args, &block)
+ end
+
+ def assert_valid_transaction_action(action)
+ unless ACTIONS.include?(action.to_sym)
+ raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
+ end
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 5fa6a0b892..1427189851 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -1,10 +1,8 @@
-require 'active_support/core_ext/array/prepend_and_append'
-
module ActiveRecord
module Validations
class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
def initialize(options)
- super(options.reverse_merge(:case_sensitive => true))
+ super({ case_sensitive: true }.merge!(options))
end
# Unfortunately, we have to tie Uniqueness validators to a class.
@@ -15,35 +13,19 @@ module ActiveRecord
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
-
- coder = record.class.serialized_attributes[attribute.to_s]
-
- if value && coder
- value = coder.dump value
- end
+ value = deserialize_attribute(record, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
-
- Array(options[:scope]).each do |scope_item|
- reflection = record.class.reflect_on_association(scope_item)
- if reflection
- scope_value = record.send(reflection.foreign_key)
- scope_item = reflection.foreign_key
- else
- scope_value = record.read_attribute(scope_item)
- end
- relation = relation.and(table[scope_item].eq(scope_value))
- end
-
+ relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
+ relation = scope_relation(record, table, relation)
relation = finder_class.unscoped.where(relation)
-
- if options[:conditions]
- relation = relation.merge(options[:conditions])
- end
+ relation.merge!(options[:conditions]) if options[:conditions]
if relation.exists?
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value))
+ error_options = options.except(:case_sensitive, :scope, :conditions)
+ error_options[:value] = value
+
+ record.errors.add(attribute, :taken, error_options)
end
end
@@ -58,7 +40,7 @@ module ActiveRecord
class_hierarchy = [record.class]
while class_hierarchy.first != @klass
- class_hierarchy.prepend(class_hierarchy.first.superclass)
+ class_hierarchy.unshift(class_hierarchy.first.superclass)
end
class_hierarchy.detect { |klass| !klass.abstract_class? }
@@ -71,18 +53,37 @@ module ActiveRecord
end
column = klass.columns_hash[attribute.to_s]
- value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
+ value = klass.connection.type_cast(value, column)
+ value = value.to_s[0, column.limit] if value && column.limit && column.text?
if !options[:case_sensitive] && value && column.text?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
- relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
+ klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
- value = klass.connection.case_sensitive_modifier(value) unless value.nil?
- relation = table[attribute].eq(value)
+ value = klass.connection.case_sensitive_modifier(value) unless value.nil?
+ table[attribute].eq(value)
+ end
+ end
+
+ def scope_relation(record, table, relation)
+ Array(options[:scope]).each do |scope_item|
+ if reflection = record.class.reflect_on_association(scope_item)
+ scope_value = record.send(reflection.foreign_key)
+ scope_item = reflection.foreign_key
+ else
+ scope_value = record.read_attribute(scope_item)
+ end
+ relation = relation.and(table[scope_item].eq(scope_value))
end
relation
end
+
+ def deserialize_attribute(record, attribute, value)
+ coder = record.class.serialized_attributes[attribute.to_s]
+ value = coder.dump value if value && coder
+ value
+ end
end
module ClassMethods
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index d5c07aecd3..ae9c74fd05 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -21,28 +21,16 @@ class <%= migration_class_name %> < ActiveRecord::Migration
end
end
<%- else -%>
- def up
+ def change
<% attributes.each do |attribute| -%>
<%- if migration_action -%>
<%- if attribute.reference? -%>
- remove_reference :<%= table_name %>, :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %>
+ remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
<%- else -%>
- remove_column :<%= table_name %>, :<%= attribute.name %>
- <%- end -%>
-<%- end -%>
-<%- end -%>
- end
-
- def down
-<% attributes.reverse.each do |attribute| -%>
-<%- if migration_action -%>
- <%- if attribute.reference? -%>
- add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
- <%- else -%>
- add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
<%- if attribute.has_index? -%>
- add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
+ remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
<%- end -%>
+ remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
<%- end -%>
<%- end -%>
<%- end -%>
diff --git a/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb b/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb
deleted file mode 100644
index e7445d03a2..0000000000
--- a/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'rails/generators/active_record'
-
-module ActiveRecord
- module Generators # :nodoc:
- class ObserverGenerator < Base # :nodoc:
- check_class_collision :suffix => "Observer"
-
- def create_observer_file
- template 'observer.rb', File.join('app/models', class_path, "#{file_name}_observer.rb")
- end
-
- hook_for :test_framework
- end
- end
-end
diff --git a/activerecord/lib/rails/generators/active_record/observer/templates/observer.rb b/activerecord/lib/rails/generators/active_record/observer/templates/observer.rb
deleted file mode 100644
index eaa256a9bd..0000000000
--- a/activerecord/lib/rails/generators/active_record/observer/templates/observer.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-<% module_namespacing do -%>
-class <%= class_name %>Observer < ActiveRecord::Observer
-end
-<% end -%>
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 94fc3564df..8812cf1b7d 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -42,7 +42,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal "DROP TABLE `people`", drop_table(:people)
end
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_create_mysql_database_with_encoding
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 534dc2c2df..ffd6904aec 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -137,6 +137,23 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_set_session_variable
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
+ session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
+ assert_equal 3, session_mode.rows.first.first.to_i
+ end
+ end
+
+ def test_mysql_set_session_variable_to_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
+ global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
+ session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
+ assert_equal global_mode.rows, session_mode.rows
+ end
+ end
+
private
def run_without_connection
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index ddfe42b375..0eb1cc511e 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -49,13 +49,11 @@ module ActiveRecord
end
def test_tables_quoting
- begin
- @conn.tables(nil, "foo-bar", nil)
- flunk
- rescue => e
- # assertion for *quoted* database properly
- assert_match(/database 'foo-bar'/, e.inspect)
- end
+ @conn.tables(nil, "foo-bar", nil)
+ flunk
+ rescue => e
+ # assertion for *quoted* database properly
+ assert_match(/database 'foo-bar'/, e.inspect)
end
def test_pk_and_sequence_for
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 14c22d2519..1265cb927e 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -53,6 +53,23 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_set_session_variable
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
+ session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
+ assert_equal 3, session_mode.rows.first.first.to_i
+ end
+ end
+
+ def test_mysql_set_session_variable_to_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
+ global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
+ session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
+ assert_equal global_mode.rows, session_mode.rows
+ end
+ end
+
def test_logs_name_structure_dump
@connection.structure_dump
assert_equal "SCHEMA", @connection.logged[0][1]
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 2c0ed73c92..94429e772f 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -37,13 +37,11 @@ module ActiveRecord
end
def test_tables_quoting
- begin
- @connection.tables(nil, "foo-bar", nil)
- flunk
- rescue => e
- # assertion for *quoted* database properly
- assert_match(/database 'foo-bar'/, e.inspect)
- end
+ @connection.tables(nil, "foo-bar", nil)
+ flunk
+ rescue => e
+ # assertion for *quoted* database properly
+ assert_match(/database 'foo-bar'/, e.inspect)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 1ff307c735..fa8f339f00 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -154,5 +154,46 @@ module ActiveRecord
end
end
+ def test_set_session_variable_true
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => true}}))
+ set_true = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
+ assert_equal set_true.rows, [["on"]]
+ end
+ end
+
+ def test_set_session_variable_false
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => false}}))
+ set_false = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
+ assert_equal set_false.rows, [["off"]]
+ end
+ end
+
+ def test_set_session_variable_nil
+ run_without_connection do |orig_connection|
+ # This should be a no-op that does not raise an error
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => nil}}))
+ end
+ end
+
+ def test_set_session_variable_default
+ run_without_connection do |orig_connection|
+ # This should execute a query that does not raise an error
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}}))
+ end
+ end
+
+ private
+
+ def run_without_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ begin
+ yield original_connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+ end
+
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index c7ce43d71e..b628b0cd90 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -30,6 +30,9 @@ end
class PostgresqlUUID < ActiveRecord::Base
end
+class PostgresqlLtree < ActiveRecord::Base
+end
+
class PostgresqlDataTypeTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/adapters/postgresql/intrange_test.rb b/activerecord/test/cases/adapters/postgresql/intrange_test.rb
new file mode 100644
index 0000000000..5f6a64619d
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/intrange_test.rb
@@ -0,0 +1,106 @@
+# encoding: utf-8
+
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlIntrangesTest < ActiveRecord::TestCase
+ class IntRangeDataType < ActiveRecord::Base
+ self.table_name = 'intrange_data_type'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ begin
+ @connection.transaction do
+ @connection.create_table('intrange_data_type') do |t|
+ t.intrange 'int_range', :default => (1..10)
+ t.intrange 'long_int_range', :limit => 8, :default => (1..100)
+ end
+ end
+ rescue ActiveRecord::StatementInvalid
+ return skip "do not test on PG without ranges"
+ end
+ @int_range_column = IntRangeDataType.columns.find { |c| c.name == 'int_range' }
+ @long_int_range_column = IntRangeDataType.columns.find { |c| c.name == 'long_int_range' }
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists intrange_data_type'
+ end
+
+ def test_columns
+ assert_equal :intrange, @int_range_column.type
+ assert_equal :intrange, @long_int_range_column.type
+ end
+
+ def test_type_cast_intrange
+ assert @int_range_column
+ assert_equal(true, @int_range_column.has_default?)
+ assert_equal((1..10), @int_range_column.default)
+ assert_equal("int4range", @int_range_column.sql_type)
+
+ data = "[1,10)"
+ hash = @int_range_column.class.string_to_intrange data
+ assert_equal((1..9), hash)
+ assert_equal((1..9), @int_range_column.type_cast(data))
+
+ assert_equal((nil..nil), @int_range_column.type_cast("empty"))
+ assert_equal((1..5), @int_range_column.type_cast('[1,5]'))
+ assert_equal((2..4), @int_range_column.type_cast('(1,5)'))
+ assert_equal((2..39), @int_range_column.type_cast('[2,40)'))
+ assert_equal((10..20), @int_range_column.type_cast('(9,20]'))
+ end
+
+ def test_type_cast_long_intrange
+ assert @long_int_range_column
+ assert_equal(true, @long_int_range_column.has_default?)
+ assert_equal((1..100), @long_int_range_column.default)
+ assert_equal("int8range", @long_int_range_column.sql_type)
+ end
+
+ def test_rewrite
+ @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 6)')"
+ x = IntRangeDataType.first
+ x.int_range = (1..100)
+ assert x.save!
+ end
+
+ def test_select
+ @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 4]')"
+ x = IntRangeDataType.first
+ assert_equal((2..4), x.int_range)
+ end
+
+ def test_empty_range
+ @connection.execute %q|insert into intrange_data_type (int_range) VALUES('empty')|
+ x = IntRangeDataType.first
+ assert_equal((nil..nil), x.int_range)
+ end
+
+ def test_rewrite_to_nil
+ @connection.execute %q|insert into intrange_data_type (int_range) VALUES('(1, 4]')|
+ x = IntRangeDataType.first
+ x.int_range = nil
+ assert x.save!
+ assert_equal(nil, x.int_range)
+ end
+
+ def test_invalid_intrange
+ assert IntRangeDataType.create!(int_range: ('a'..'d'))
+ x = IntRangeDataType.first
+ assert_equal(nil, x.int_range)
+ end
+
+ def test_save_empty_range
+ assert IntRangeDataType.create!(int_range: (nil..nil))
+ x = IntRangeDataType.first
+ assert_equal((nil..nil), x.int_range)
+ end
+
+ def test_save_invalid_data
+ assert_raises(ActiveRecord::StatementInvalid) do
+ IntRangeDataType.create!(int_range: "empty1")
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
new file mode 100644
index 0000000000..5d12ca75ca
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -0,0 +1,41 @@
+# encoding: utf-8
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlLtreeTest < ActiveRecord::TestCase
+ class Ltree < ActiveRecord::Base
+ self.table_name = 'ltrees'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.create_table('ltrees') do |t|
+ t.ltree 'path'
+ end
+ end
+ rescue ActiveRecord::StatementInvalid
+ skip "do not test on PG without ltree"
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists ltrees'
+ end
+
+ def test_column
+ column = Ltree.columns_hash['path']
+ assert_equal :ltree, column.type
+ end
+
+ def test_write
+ ltree = Ltree.new(path: '1.2.3.4')
+ assert ltree.save!
+ end
+
+ def test_select
+ @connection.execute "insert into ltrees (path) VALUES ('1.2.3')"
+ ltree = Ltree.first
+ assert_equal '1.2.3', ltree.path
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index f1362dd15f..872204c644 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -216,6 +216,35 @@ module ActiveRecord
assert_equal "(number > 100)", index.where
end
+ def test_distinct_zero_orders
+ assert_equal "DISTINCT posts.id",
+ @connection.distinct("posts.id", [])
+ end
+
+ def test_distinct_one_order
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", ["posts.created_at desc"])
+ end
+
+ def test_distinct_few_orders
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0, posts.position AS alias_1",
+ @connection.distinct("posts.id", ["posts.created_at desc", "posts.position asc"])
+ end
+
+ def test_distinct_blank_not_nil_orders
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", ["posts.created_at desc", "", " "])
+ end
+
+ def test_distinct_with_arel_order
+ order = Object.new
+ def order.to_sql
+ "posts.created_at desc"
+ end
+ assert_equal "DISTINCT posts.id, posts.created_at AS alias_0",
+ @connection.distinct("posts.id", [order])
+ end
+
def test_distinct_with_nulls
assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"])
assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
diff --git a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
new file mode 100644
index 0000000000..d7d40f6385
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb
@@ -0,0 +1,18 @@
+require "cases/helper"
+
+class SqlTypesTest < ActiveRecord::TestCase
+ def test_binary_types
+ assert_equal 'bytea', type_to_sql(:binary, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ type_to_sql :binary, 4294967295
+ end
+ assert_equal 'text', type_to_sql(:text, 100_000)
+ assert_raise ActiveRecord::ActiveRecordError do
+ type_to_sql :text, 4294967295
+ end
+ end
+
+ def type_to_sql(*args)
+ ActiveRecord::Base.connection.type_to_sql(*args)
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
index 26507ad654..dbc69a529c 100644
--- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb
@@ -1,7 +1,16 @@
require 'cases/helper'
require 'models/developer'
+require 'models/topic'
class TimestampTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_group_by_date
+ keys = Topic.group("date_trunc('month', created_at)").count.keys
+ assert_operator keys.length, :>, 0
+ keys.each { |k| assert_kind_of Time, k }
+ end
+
def test_load_infinity_and_beyond
unless current_adapter?(:PostgreSQLAdapter)
return skip("only tested on postgresql")
@@ -75,6 +84,15 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal '4', pg_datetime_precision('foos', 'updated_at')
end
+ def test_bc_timestamp
+ unless current_adapter?(:PostgreSQLAdapter)
+ return skip("only tested on postgresql")
+ end
+ date = Date.new(0) - 1.second
+ Developer.create!(:name => "aaron", :updated_at => date)
+ assert_equal date, Developer.find_by_name("aaron").updated_at
+ end
+
private
def pg_datetime_precision(table_name, column_name)
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 48b06a767f..10195e3ae4 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -1,6 +1,5 @@
require "cases/helper"
require 'models/customer'
-require 'active_support/core_ext/exception'
class AggregationsTest < ActiveRecord::TestCase
fixtures :customers
@@ -26,7 +25,7 @@ class AggregationsTest < ActiveRecord::TestCase
def test_immutable_value_objects
customers(:david).balance = Money.new(100)
- assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } }
+ assert_raise(RuntimeError) { customers(:david).balance.instance_eval { @amount = 20 } }
end
def test_inferred_mapping
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 5f7825783b..3a6da0e59f 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -33,7 +33,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_with_primary_key_joins_on_correct_column
sql = Client.joins(:firm_with_primary_key).to_sql
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
elsif current_adapter?(:OracleAdapter)
@@ -63,6 +63,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.id, citibank.firm_id
end
+ def test_id_assignment
+ apple = Firm.create("name" => "Apple")
+ citibank = Account.create("credit_limit" => 10)
+ citibank.firm_id = apple
+ assert_nil citibank.firm_id
+ end
+
def test_natural_assignment_with_primary_key
apple = Firm.create("name" => "Apple")
citibank = Client.create("name" => "Primary key client")
@@ -109,6 +116,34 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.id, citibank.firm_id
end
+ def test_building_the_belonging_object_with_implicit_sti_base_class
+ account = Account.new
+ company = account.build_firm
+ assert_kind_of Company, company, "Expected #{company.class} to be a Company"
+ end
+
+ def test_building_the_belonging_object_with_explicit_sti_base_class
+ account = Account.new
+ company = account.build_firm(:type => "Company")
+ assert_kind_of Company, company, "Expected #{company.class} to be a Company"
+ end
+
+ def test_building_the_belonging_object_with_sti_subclass
+ account = Account.new
+ company = account.build_firm(:type => "Firm")
+ assert_kind_of Firm, company, "Expected #{company.class} to be a Firm"
+ end
+
+ def test_building_the_belonging_object_with_an_invalid_type
+ account = Account.new
+ assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(:type => "InvalidType") }
+ end
+
+ def test_building_the_belonging_object_with_an_unrelated_type
+ account = Account.new
+ assert_raise(ActiveRecord::SubclassNotFound) { account.build_firm(:type => "Account") }
+ end
+
def test_building_the_belonging_object_with_primary_key
client = Client.create(:name => "Primary key client")
apple = client.build_firm_with_primary_key("name" => "Apple")
@@ -289,12 +324,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Topic.find(topic.id)[:replies_count]
end
- def test_belongs_to_counter_after_update_attributes
- topic = Topic.create!(:title => "37s")
- topic.replies.create!(:title => "re: 37s", :content => "rails")
+ def test_belongs_to_counter_after_update
+ topic = Topic.create!(title: "37s")
+ topic.replies.create!(title: "re: 37s", content: "rails")
assert_equal 1, Topic.find(topic.id)[:replies_count]
- topic.update_attributes(:title => "37signals")
+ topic.update(title: "37signals")
assert_equal 1, Topic.find(topic.id)[:replies_count]
end
@@ -539,6 +574,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal new_firm.name, "Apple"
end
+ def test_attributes_are_set_without_error_when_initialized_from_belongs_to_association_with_array_in_where_clause
+ new_account = Account.where(:credit_limit => [ 50, 60 ]).new
+ assert_nil new_account.credit_limit
+ end
+
def test_reassigning_the_parent_id_updates_the_object
client = companies(:second_client)
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 124bf65d3a..ce1da53859 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -212,8 +212,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
post = posts(:welcome)
- post.update_attributes!(:author => nil)
- post = assert_queries(1) { Post.all.merge!(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address
+ post.update!(author: nil)
+ post = assert_queries(1) { Post.all.merge!(includes: {author_with_address: :author_address}).find(post.id) }
+ # find the post, then find the author which is null so no query for the author or address
assert_no_queries do
assert_equal nil, post.author_with_address
end
@@ -221,7 +222,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_polymorphic_association
sponsor = sponsors(:moustache_club_sponsor_for_groucho)
- sponsor.update_attributes!(:sponsorable => nil)
+ sponsor.update!(sponsorable: nil)
sponsor = assert_queries(1) { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) }
assert_no_queries do
assert_equal nil, sponsor.sponsorable
@@ -616,8 +617,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
general = categories.find { |c| c == categories(:general) }
technology = categories.find { |c| c == categories(:technology) }
- post1 = general.posts.to_a.find { |p| p == posts(:welcome) }
- post2 = technology.posts.to_a.find { |p| p == posts(:welcome) }
+ post1 = general.posts.to_a.find { |p| p == welcome }
+ post2 = technology.posts.to_a.find { |p| p == welcome }
assert_equal post1.object_id, post2.object_id
end
@@ -944,6 +945,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 3, Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a.size
end
+ def test_dont_create_temporary_active_record_instances
+ Developer.instance_count = 0
+ developers = Developer.all.merge!(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).to_a
+ assert_equal developers.count, Developer.instance_count
+ end
+
def test_order_on_join_table_with_include_and_limit
assert_equal 5, Developer.all.merge!(:includes => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).to_a.size
end
@@ -984,10 +991,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
post = Post.create!(:title => 'Beaches', :body => "I like beaches!")
Reader.create! :person => people(:david), :post => post
LazyReader.create! :person => people(:susan), :post => post
-
+
assert_equal 1, post.lazy_readers.to_a.size
assert_equal 2, post.lazy_readers_skimmers_or_not.to_a.size
-
+
post_with_readers = Post.includes(:lazy_readers_skimmers_or_not).find(post.id)
assert_equal 2, post_with_readers.lazy_readers_skimmers_or_not.to_a.size
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 01afa087be..7e6c7d5862 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -144,6 +144,34 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'defaulty', bulb.name
end
+ def test_building_the_associated_object_with_implicit_sti_base_class
+ firm = DependentFirm.new
+ company = firm.companies.build
+ assert_kind_of Company, company, "Expected #{company.class} to be a Company"
+ end
+
+ def test_building_the_associated_object_with_explicit_sti_base_class
+ firm = DependentFirm.new
+ company = firm.companies.build(:type => "Company")
+ assert_kind_of Company, company, "Expected #{company.class} to be a Company"
+ end
+
+ def test_building_the_associated_object_with_sti_subclass
+ firm = DependentFirm.new
+ company = firm.companies.build(:type => "Client")
+ assert_kind_of Client, company, "Expected #{company.class} to be a Client"
+ end
+
+ def test_building_the_associated_object_with_an_invalid_type
+ firm = DependentFirm.new
+ assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(:type => "Invalid") }
+ end
+
+ def test_building_the_associated_object_with_an_unrelated_type
+ firm = DependentFirm.new
+ assert_raise(ActiveRecord::SubclassNotFound) { firm.companies.build(:type => "Account") }
+ end
+
def test_association_keys_bypass_attribute_protection
car = Car.create(:name => 'honda')
@@ -270,12 +298,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, Firm.order(:id).find{|f| f.id > 0}.clients.length
end
- def test_find_with_blank_conditions
- [[], {}, nil, ""].each do |blank|
- assert_equal 2, Firm.all.merge!(:order => "id").first.clients.where(blank).to_a.size
- end
- end
-
def test_find_many_with_merged_options
assert_equal 1, companies(:first_firm).limited_clients.size
assert_equal 1, companies(:first_firm).limited_clients.to_a.size
@@ -1579,6 +1601,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [tagging], post.taggings
end
+ def test_build_with_polymorphic_has_many_does_not_allow_to_override_type_and_id
+ welcome = posts(:welcome)
+ tagging = welcome.taggings.build(:taggable_id => 99, :taggable_type => 'ShouldNotChange')
+
+ assert_equal welcome.id, tagging.taggable_id
+ assert_equal 'Post', tagging.taggable_type
+ end
+
def test_dont_call_save_callbacks_twice_on_has_many
firm = companies(:first_firm)
contract = firm.contracts.create!
@@ -1658,6 +1688,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_deprecated { klass.has_many :foo, :counter_sql => 'lol' }
end
+ test "sum calculation with block for array compatibility is deprecated" do
+ assert_deprecated do
+ posts(:welcome).comments.sum { |c| c.id }
+ end
+ end
+
test "has many associations on new records use null relations" do
post = Post.new
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 8e52ce1d91..af91fb2920 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -330,6 +330,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_update_counter_caches_on_replace_association
+ post = posts(:welcome)
+ tag = post.tags.create!(:name => 'doomed')
+ tag.tagged_posts << posts(:thinking)
+
+ tag.tagged_posts = []
+ post.reload
+
+ assert_equal(post.taggings.count, post.taggings_count)
+ end
+
def test_replace_association
assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
@@ -695,7 +706,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_can_update_through_association
assert_nothing_raised do
- people(:michael).posts.first.update_attributes!(:title => "Can write")
+ people(:michael).posts.first.update!(title: "Can write")
end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index ea1cfa0805..4ed09a3bf7 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -6,6 +6,8 @@ require 'models/ship'
require 'models/pirate'
require 'models/car'
require 'models/bulb'
+require 'models/author'
+require 'models/post'
class HasOneAssociationsTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false unless supports_savepoints?
@@ -212,6 +214,34 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
}
end
+ def test_building_the_associated_object_with_implicit_sti_base_class
+ firm = DependentFirm.new
+ company = firm.build_company
+ assert_kind_of Company, company, "Expected #{company.class} to be a Company"
+ end
+
+ def test_building_the_associated_object_with_explicit_sti_base_class
+ firm = DependentFirm.new
+ company = firm.build_company(:type => "Company")
+ assert_kind_of Company, company, "Expected #{company.class} to be a Company"
+ end
+
+ def test_building_the_associated_object_with_sti_subclass
+ firm = DependentFirm.new
+ company = firm.build_company(:type => "Client")
+ assert_kind_of Client, company, "Expected #{company.class} to be a Client"
+ end
+
+ def test_building_the_associated_object_with_an_invalid_type
+ firm = DependentFirm.new
+ assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(:type => "Invalid") }
+ end
+
+ def test_building_the_associated_object_with_an_unrelated_type
+ firm = DependentFirm.new
+ assert_raise(ActiveRecord::SubclassNotFound) { firm.build_company(:type => "Account") }
+ end
+
def test_build_and_create_should_not_happen_within_scope
pirate = pirates(:blackbeard)
scoped_count = pirate.association(:foo_bulb).scope.where_values.count
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 9b00c21b52..10ec33be75 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -443,8 +443,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
author = Author.first
- assert_present author.comments
- assert_blank author.nonexistant_comments
+ assert author.comments.present?
+ assert author.nonexistant_comments.blank?
end
def test_has_many_through_uses_correct_attributes
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index c0f1945cec..d7f25f760e 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -289,6 +289,14 @@ class OverridingAssociationsTest < ActiveRecord::TestCase
DifferentPeopleList.reflect_on_association(:has_one)
)
end
+
+ def test_requires_symbol_argument
+ assert_raises ArgumentError do
+ Class.new(Post) do
+ belongs_to "author"
+ end
+ end
+ end
end
class GeneratedMethodsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 16ce150396..e5cb4f8f7a 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -161,16 +161,16 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_callbacks_firing_order_on_update
- eye = Eye.create(:iris_attributes => {:color => 'honey'})
- eye.update_attributes(:iris_attributes => {:color => 'green'})
+ eye = Eye.create(iris_attributes: {color: 'honey'})
+ eye.update(iris_attributes: {color: 'green'})
assert_equal [true, false], eye.after_update_callbacks_stack
end
def test_callbacks_firing_order_on_save
- eye = Eye.create(:iris_attributes => {:color => 'honey'})
+ eye = Eye.create(iris_attributes: {color: 'honey'})
assert_equal [false, false], eye.after_save_callbacks_stack
- eye.update_attributes(:iris_attributes => {:color => 'blue'})
+ eye.update(iris_attributes: {color: 'blue'})
assert_equal [false, false, false, false], eye.after_save_callbacks_stack
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 8644f2f496..4f46459ab3 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -23,8 +23,9 @@ require 'models/edge'
require 'models/joke'
require 'models/bulb'
require 'models/bird'
+require 'models/car'
+require 'models/bulb'
require 'rexml/document'
-require 'active_support/core_ext/exception'
class FirstAbstractClass < ActiveRecord::Base
self.abstract_class = true
@@ -123,7 +124,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil Edge.primary_key
end
- unless current_adapter?(:PostgreSQLAdapter,:OracleAdapter,:SQLServerAdapter)
+ unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
def test_limit_with_comma
assert Topic.limit("1,2").to_a
end
@@ -152,7 +153,7 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- unless current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
+ unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_limit_should_allow_sql_literal
assert_equal 1, Topic.limit(Arel.sql('2-1')).to_a.length
end
@@ -221,7 +222,7 @@ class BasicsTest < ActiveRecord::TestCase
)
# For adapters which support microsecond resolution.
- if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter)
+ if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
@@ -299,13 +300,11 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_initialize_with_invalid_attribute
- begin
- Topic.new({ "title" => "test",
- "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
- rescue ActiveRecord::MultiparameterAssignmentErrors => ex
- assert_equal(1, ex.errors.size)
- assert_equal("last_read", ex.errors[0].attribute)
- end
+ Topic.new({ "title" => "test",
+ "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
+ rescue ActiveRecord::MultiparameterAssignmentErrors => ex
+ assert_equal(1, ex.errors.size)
+ assert_equal("last_read", ex.errors[0].attribute)
end
def test_create_after_initialize_without_block
@@ -470,7 +469,7 @@ class BasicsTest < ActiveRecord::TestCase
Post.reset_table_name
end
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_update_all_with_order_and_limit
assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!')
end
@@ -592,7 +591,7 @@ class BasicsTest < ActiveRecord::TestCase
post.reload
assert_equal "cannot change this", post.title
- post.update_attributes(:title => "try to change", :body => "changed")
+ post.update(title: "try to change", body: "changed")
post.reload
assert_equal "cannot change this", post.title
assert_equal "changed", post.body
@@ -1000,7 +999,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_reload_with_exclusive_scope
dev = DeveloperCalledDavid.first
- dev.update_attributes!( :name => "NotDavid" )
+ dev.update!(name: "NotDavid" )
assert_equal dev, dev.reload
end
@@ -1442,6 +1441,21 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
end
+ def test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format
+ dev = CachedDeveloper.first
+ assert_equal "cached_developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
+ end
+
+ def test_cache_key_changes_when_child_touched
+ car = Car.create
+ Bulb.create(car: car)
+
+ key = car.cache_key
+ car.bulb.touch
+ car.reload
+ assert_not_equal key, car.cache_key
+ end
+
def test_cache_key_format_for_existing_record_with_nil_updated_at
dev = Developer.first
dev.update_columns(updated_at: nil)
@@ -1467,7 +1481,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_column_types_typecast
topic = Topic.first
- refute_equal 't.lo', topic.author_name
+ assert_not_equal 't.lo', topic.author_name
attrs = topic.attributes.dup
attrs.delete 'id'
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 65d28ea028..b7622705bf 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -8,6 +8,7 @@ require 'models/possession'
require 'models/topic'
require 'models/minivan'
require 'models/speedometer'
+require 'models/ship_part'
Company.has_many :accounts
@@ -33,8 +34,9 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_return_integer_average_if_db_returns_such
- Account.connection.stubs :select_value => 3
- value = Account.average(:id)
+ ShipPart.delete_all
+ ShipPart.create!(:id => 3, :name => 'foo')
+ value = ShipPart.average(:id)
assert_equal 3, value
end
@@ -383,30 +385,16 @@ 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),
Account.where("credit_limit > 50").from('accounts').sum(:credit_limit)
end
- def test_sum_array_compatibility
- assert_equal Account.sum(:credit_limit), Account.sum(&:credit_limit)
+ def test_sum_array_compatibility_deprecation
+ assert_deprecated do
+ assert_equal Account.sum(:credit_limit), Account.sum(&:credit_limit)
+ end
end
def test_average_with_from_option
@@ -430,34 +418,19 @@ class CalculationsTest < ActiveRecord::TestCase
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) || current_adapter?(:MysqlAdapter)
- assert_equal "7", Company.includes(:contracts).maximum(:developer_id)
- else
- assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
- end
+ assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
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) || current_adapter?(:MysqlAdapter)
- assert_equal "7", Company.includes(:contracts).minimum(:developer_id)
- else
- assert_equal 7, Company.includes(:contracts).minimum(:developer_id)
- end
+ assert_equal 7, Company.includes(:contracts).minimum(:developer_id)
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
+ assert_equal 7, Company.includes(:contracts).sum(:developer_id)
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index 2124c256fa..dc1b30261c 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/company'
module ActiveRecord
module ConnectionAdapters
@@ -40,13 +41,20 @@ module ActiveRecord
def test_type_cast_non_integer_to_integer
column = Column.new("field", nil, "integer")
- assert_raises(NoMethodError) do
- column.type_cast([])
- end
+ assert_nil column.type_cast([1,2])
+ assert_nil column.type_cast({1 => 2})
+ assert_nil column.type_cast((1..2))
+ end
- assert_raises(NoMethodError) do
- column.type_cast(Object.new)
- end
+ def test_type_cast_activerecord_to_integer
+ column = Column.new("field", nil, "integer")
+ firm = Firm.create(:name => 'Apple')
+ assert_nil column.type_cast(firm)
+ end
+
+ def test_type_cast_object_without_to_i_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_nil column.type_cast(Object.new)
end
def test_type_cast_time
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
index 3e3d6e2769..1fd64dd0af 100644
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
@@ -10,19 +10,18 @@ module ActiveRecord
end
def test_in_use?
- # FIXME: change to refute in Rails 4.0 / mt
- assert !adapter.in_use?, 'adapter is not in use'
+ assert_not adapter.in_use?, 'adapter is not in use'
assert adapter.lease, 'lease adapter'
assert adapter.in_use?, 'adapter is in use'
end
def test_lease_twice
assert adapter.lease, 'should lease adapter'
- assert !adapter.lease, 'should not lease adapter'
+ assert_not adapter.lease, 'should not lease adapter'
end
def test_last_use
- assert !adapter.last_use
+ assert_not adapter.last_use
adapter.lease
assert adapter.last_use
end
@@ -31,7 +30,7 @@ module ActiveRecord
assert adapter.lease, 'lease adapter'
assert adapter.in_use?, 'adapter is in use'
adapter.expire
- assert !adapter.in_use?, 'adapter is in use'
+ assert_not adapter.in_use?, 'adapter is in use'
end
def test_close
@@ -45,7 +44,7 @@ module ActiveRecord
# Close should put the adapter back in the pool
adapter.close
- assert !adapter.in_use?
+ assert_not adapter.in_use?
assert_equal adapter, pool.connection
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 2ddabe058f..3e33b30144 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -4,8 +4,8 @@ module ActiveRecord
module ConnectionAdapters
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
- @klass = Class.new(Base)
- @subklass = Class.new(@klass)
+ @klass = Class.new(Base) { def self.name; 'klass'; end }
+ @subklass = Class.new(@klass) { def self.name; 'subklass'; end }
@handler = ConnectionHandler.new
@pool = @handler.establish_connection(@klass, Base.connection_pool.spec)
@@ -36,13 +36,11 @@ module ActiveRecord
end
def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove
- @handler.establish_connection 'north america', Base.connection_pool.spec
- assert_same @handler.retrieve_connection_pool(@klass),
- @handler.retrieve_connection_pool(@subklass)
+ sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec)
+ assert_same sub_pool, @handler.retrieve_connection_pool(@subklass)
@handler.remove_connection @subklass
- assert_same @handler.retrieve_connection_pool(@klass),
- @handler.retrieve_connection_pool(@subklass)
+ assert_same @pool, @handler.retrieve_connection_pool(@subklass)
end
def test_connection_pools
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index ee9818678d..52de0efe7f 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -9,7 +9,7 @@ module ActiveRecord
end
def test_url_host_no_db
- skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
spec = resolve 'mysql://foo?encoding=utf8'
assert_equal({
:adapter => "mysql",
@@ -18,7 +18,7 @@ module ActiveRecord
end
def test_url_host_db
- skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
spec = resolve 'mysql://foo/bar?encoding=utf8'
assert_equal({
:adapter => "mysql",
@@ -28,7 +28,7 @@ module ActiveRecord
end
def test_url_port
- skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
spec = resolve 'mysql://foo:123?encoding=utf8'
assert_equal({
:adapter => "mysql",
@@ -38,7 +38,7 @@ module ActiveRecord
end
def test_encoded_password
- skip "only if mysql is available" unless current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ skip "only if mysql is available" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
password = 'am@z1ng_p@ssw0rd#!'
encoded_password = URI.encode_www_form_component(password)
spec = resolve "mysql://foo:#{encoded_password}@localhost/bar"
diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb
index 3deb0dac99..427076bd80 100644
--- a/activerecord/test/cases/date_time_test.rb
+++ b/activerecord/test/cases/date_time_test.rb
@@ -8,15 +8,15 @@ class DateTimeTest < ActiveRecord::TestCase
with_active_record_default_timezone :utc do
time_values = [1807, 2, 10, 15, 30, 45]
# create DateTime value with local time zone offset
- local_offset = Rational(Time.local_time(*time_values).utc_offset, 86400)
+ local_offset = Rational(Time.local(*time_values).utc_offset, 86400)
now = DateTime.civil(*(time_values + [local_offset]))
task = Task.new
task.starting = now
task.save!
- # check against Time.local_time, since some platforms will return a Time instead of a DateTime
- assert_equal Time.local_time(*time_values), Task.find(task.id).starting
+ # check against Time.local, since some platforms will return a Time instead of a DateTime
+ assert_equal Time.local(*time_values), Task.find(task.id).starting
end
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index ed7eedaa27..e0cf4adf13 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -39,7 +39,7 @@ class DefaultTest < ActiveRecord::TestCase
end
end
-if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional fixtures mode, this will
diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
index dde36e7f72..32eb87d522 100644
--- a/activerecord/test/cases/deprecated_dynamic_methods_test.rb
+++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -568,9 +568,9 @@ class DynamicScopeTest < ActiveRecord::TestCase
end
def test_dynamic_scope_should_create_methods_after_hitting_method_missing
- assert_blank @test_klass.methods.grep(/scoped_by_type/)
+ assert @test_klass.methods.grep(/scoped_by_type/).blank?
@test_klass.scoped_by_type(nil)
- assert_present @test_klass.methods.grep(/scoped_by_type/)
+ assert @test_klass.methods.grep(/scoped_by_type/).present?
end
def test_dynamic_scope_with_less_number_of_arguments
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index d4fc5f204b..b9961a4420 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -12,7 +12,7 @@ class Pirate # Just reopening it, not defining it
after_update :check_changes
private
- # after_save/update in sweepers, observers, and the model itself
+ # after_save/update and the model itself
# can end up checking dirty status and acting on the results
def check_changes
if self.changed?
@@ -203,6 +203,20 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_nullable_datetime_not_marked_as_changed_if_new_value_is_blank
+ in_time_zone 'Edinburgh' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'topics'
+
+ topic = target.create
+ assert_nil topic.written_on
+
+ topic.written_on = ""
+ assert_nil topic.written_on
+ assert !topic.written_on_changed?
+ end
+ end
+
def test_integer_zero_to_string_zero_not_marked_as_changed
pirate = Pirate.new
pirate.parrot_id = 0
@@ -503,7 +517,7 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('created_on')
pirate = Pirate.find_by_catchphrase("Thar She Blows!")
- pirate.update_attributes(:catchphrase => "Ahoy!")
+ pirate.update(catchphrase: "Ahoy!")
assert_equal 2, pirate.previous_changes.size
assert_equal ["Thar She Blows!", "Ahoy!"], pirate.previous_changes['catchphrase']
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index 6dce8ccdd1..aa2a6d7509 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -108,11 +108,21 @@ if ActiveRecord::Base.connection.supports_explain?
assert_equal expected, base.exec_explain(queries)
end
+ def test_unsupported_connection_adapter
+ connection.stubs(:supports_explain?).returns(false)
+
+ base.logger.expects(:warn).never
+
+ with_threshold(0) do
+ Car.where(:name => 'honda').to_a
+ end
+ end
+
def test_silence_auto_explain
base.expects(:collecting_sqls_for_explain).never
base.logger.expects(:warn).never
base.silence_auto_explain do
- with_threshold(0) { Car.all }
+ with_threshold(0) { Car.all.to_a }
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 7db7953313..a9fa107749 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -15,6 +15,18 @@ require 'models/toy'
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations
+ def test_find_by_id_with_hash
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Post.find_by_id(:limit => 1)
+ end
+ end
+
+ def test_find_by_title_and_id_with_hash
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Post.find_by_title_and_id('foo', :limit => 1)
+ end
+ end
+
def test_find
assert_equal(topics(:first).title, Topic.find(1).title)
end
diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb
index 9a2172f41e..490b599fb6 100644
--- a/activerecord/test/cases/forbidden_attributes_protection_test.rb
+++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'active_support/core_ext/hash/indifferent_access'
require 'models/person'
+require 'models/company'
class ProtectedParams < ActiveSupport::HashWithIndifferentAccess
attr_accessor :permitted
@@ -40,6 +41,20 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase
assert_equal 'm', person.gender
end
+ def test_forbidden_attributes_cannot_be_used_for_sti_inheritance_column
+ params = ProtectedParams.new(type: 'Client')
+ assert_raises(ActiveModel::ForbiddenAttributesError) do
+ Company.new(params)
+ end
+ end
+
+ def test_permitted_attributes_can_be_used_for_sti_inheritance_column
+ params = ProtectedParams.new(type: 'Client')
+ params.permit!
+ person = Company.new(params)
+ assert_equal person.class, Client
+ end
+
def test_regular_hash_should_still_be_used_for_mass_assignment
person = Person.new(first_name: 'Guille', gender: 'm')
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 1bff005510..5ffb32e809 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -2,8 +2,7 @@ require File.expand_path('../../../../load_paths', __FILE__)
require 'config'
-gem 'minitest'
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'stringio'
require 'active_record'
@@ -22,8 +21,6 @@ ActiveSupport::Deprecation.debug = true
# Connect to the database
ARTest.connect
-require 'support/mysql'
-
# Quote "type" if it's a reserved word for the current connection.
QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type')
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index aab7aa51dd..189066eb41 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -156,6 +156,29 @@ class InheritanceTest < ActiveRecord::TestCase
assert_kind_of Cabbage, savoy
end
+ def test_inheritance_new_with_default_class
+ company = Company.new
+ assert_equal Company, company.class
+ end
+
+ def test_inheritance_new_with_base_class
+ company = Company.new(:type => 'Company')
+ assert_equal Company, company.class
+ end
+
+ def test_inheritance_new_with_subclass
+ firm = Company.new(:type => 'Firm')
+ assert_equal Firm, firm.class
+ end
+
+ def test_new_with_invalid_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'InvalidType') }
+ end
+
+ def test_new_with_unrelated_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') }
+ end
+
def test_inheritance_condition
assert_equal 10, Company.count
assert_equal 2, Firm.count
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 8f1cdd47ea..be59ffc4ab 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -17,6 +17,37 @@ module ActiveRecord
end
end
+ class InvertibleRevertMigration < SilentMigration
+ def change
+ revert do
+ create_table("horses") do |t|
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ end
+ end
+ end
+
+ class InvertibleByPartsMigration < SilentMigration
+ attr_writer :test
+ def change
+ create_table("new_horses") do |t|
+ t.column :breed, :string
+ end
+ reversible do |dir|
+ @test.yield :both
+ dir.up { @test.yield :up }
+ dir.down { @test.yield :down }
+ end
+ revert do
+ create_table("horses") do |t|
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ end
+ end
+ end
+
class NonInvertibleMigration < SilentMigration
def change
create_table("horses") do |t|
@@ -40,9 +71,28 @@ module ActiveRecord
end
end
+ class RevertWholeMigration < SilentMigration
+ def initialize(name = self.class.name, version = nil, migration)
+ @migration = migration
+ super(name, version)
+ end
+
+ def change
+ revert @migration
+ end
+ end
+
+ class NestedRevertWholeMigration < RevertWholeMigration
+ def change
+ revert { super }
+ end
+ end
+
def teardown
- if ActiveRecord::Base.connection.table_exists?("horses")
- ActiveRecord::Base.connection.drop_table("horses")
+ %w[horses new_horses].each do |table|
+ if ActiveRecord::Base.connection.table_exists?(table)
+ ActiveRecord::Base.connection.drop_table(table)
+ end
end
end
@@ -67,6 +117,83 @@ module ActiveRecord
assert !migration.connection.table_exists?("horses")
end
+ def test_migrate_revert
+ migration = InvertibleMigration.new
+ revert = InvertibleRevertMigration.new
+ migration.migrate :up
+ revert.migrate :up
+ assert !migration.connection.table_exists?("horses")
+ revert.migrate :down
+ assert migration.connection.table_exists?("horses")
+ migration.migrate :down
+ assert !migration.connection.table_exists?("horses")
+ end
+
+ def test_migrate_revert_by_part
+ InvertibleMigration.new.migrate :up
+ received = []
+ migration = InvertibleByPartsMigration.new
+ migration.test = ->(dir){
+ assert migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
+ received << dir
+ }
+ migration.migrate :up
+ assert_equal [:both, :up], received
+ assert !migration.connection.table_exists?("horses")
+ assert migration.connection.table_exists?("new_horses")
+ migration.migrate :down
+ assert_equal [:both, :up, :both, :down], received
+ assert migration.connection.table_exists?("horses")
+ assert !migration.connection.table_exists?("new_horses")
+ end
+
+ def test_migrate_revert_whole_migration
+ migration = InvertibleMigration.new
+ [LegacyMigration, InvertibleMigration].each do |klass|
+ revert = RevertWholeMigration.new(klass)
+ migration.migrate :up
+ revert.migrate :up
+ assert !migration.connection.table_exists?("horses")
+ revert.migrate :down
+ assert migration.connection.table_exists?("horses")
+ migration.migrate :down
+ assert !migration.connection.table_exists?("horses")
+ end
+ end
+
+ def test_migrate_nested_revert_whole_migration
+ revert = NestedRevertWholeMigration.new(InvertibleRevertMigration)
+ revert.migrate :down
+ assert revert.connection.table_exists?("horses")
+ revert.migrate :up
+ assert !revert.connection.table_exists?("horses")
+ end
+
+ def test_revert_order
+ block = Proc.new{|t| t.string :name }
+ recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection)
+ recorder.instance_eval do
+ create_table("apples", &block)
+ revert do
+ create_table("bananas", &block)
+ revert do
+ create_table("clementines")
+ create_table("dates")
+ end
+ create_table("elderberries")
+ end
+ revert do
+ create_table("figs")
+ create_table("grapes")
+ end
+ end
+ assert_equal [[:create_table, ["apples"], block], [:drop_table, ["elderberries"], nil],
+ [:create_table, ["clementines"], nil], [:create_table, ["dates"], nil],
+ [:drop_table, ["bananas"], block], [:drop_table, ["grapes"], nil],
+ [:drop_table, ["figs"], nil]], recorder.commands
+ end
+
def test_legacy_up
LegacyMigration.migrate :up
assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
deleted file mode 100644
index 0b78f2e46b..0000000000
--- a/activerecord/test/cases/lifecycle_test.rb
+++ /dev/null
@@ -1,256 +0,0 @@
-require 'cases/helper'
-require 'models/topic'
-require 'models/developer'
-require 'models/reply'
-require 'models/minimalistic'
-require 'models/comment'
-
-class SpecialDeveloper < Developer; end
-
-class DeveloperObserver < ActiveRecord::Observer
- def calls
- @calls ||= []
- end
-
- def before_save(developer)
- calls << developer
- end
-end
-
-class SalaryChecker < ActiveRecord::Observer
- observe :special_developer
- attr_accessor :last_saved
-
- def before_save(developer)
- return developer.salary > 80000
- end
-
- module Implementation
- def after_save(developer)
- self.last_saved = developer
- end
- end
- include Implementation
-
-end
-
-class TopicaAuditor < ActiveRecord::Observer
- observe :topic
-
- attr_reader :topic
-
- def after_find(topic)
- @topic = topic
- end
-end
-
-class TopicObserver < ActiveRecord::Observer
- attr_reader :topic
-
- def after_find(topic)
- @topic = topic
- end
-
- # Create an after_save callback, so a notify_observer hook is created
- # on :topic.
- def after_save(nothing)
- end
-end
-
-class MinimalisticObserver < ActiveRecord::Observer
- attr_reader :minimalistic
-
- def after_find(minimalistic)
- @minimalistic = minimalistic
- end
-end
-
-class MultiObserver < ActiveRecord::Observer
- attr_reader :record
-
- def self.observed_class() [ Topic, Developer ] end
-
- cattr_reader :last_inherited
- @@last_inherited = nil
-
- def observed_class_inherited_with_testing(subclass)
- observed_class_inherited_without_testing(subclass)
- @@last_inherited = subclass
- end
-
- alias_method_chain :observed_class_inherited, :testing
-
- def after_find(record)
- @record = record
- end
-end
-
-class ValidatedComment < Comment
- attr_accessor :callers
-
- before_validation :record_callers
-
- after_validation do
- record_callers
- end
-
- def record_callers
- callers << self.class if callers
- end
-end
-
-class ValidatedCommentObserver < ActiveRecord::Observer
- attr_accessor :callers
-
- def after_validation(model)
- callers << self.class if callers
- end
-end
-
-
-class AroundTopic < Topic
-end
-
-class AroundTopicObserver < ActiveRecord::Observer
- observe :around_topic
- def topic_ids
- @topic_ids ||= []
- end
-
- def around_save(topic)
- topic_ids << topic.id
- yield(topic)
- topic_ids << topic.id
- end
-end
-
-class LifecycleTest < ActiveRecord::TestCase
- fixtures :topics, :developers, :minimalistics
-
- def test_before_destroy
- topic = Topic.find(1)
- assert_difference 'Topic.count', -(1 + topic.replies.size) do
- topic.destroy
- end
- end
-
- def test_auto_observer
- topic_observer = TopicaAuditor.instance
- assert_nil TopicaAuditor.observed_class
- assert_equal [Topic], TopicaAuditor.observed_classes.to_a
-
- topic = Topic.find(1)
- assert_equal topic.title, topic_observer.topic.title
- end
-
- def test_inferred_auto_observer
- topic_observer = TopicObserver.instance
- assert_equal Topic, TopicObserver.observed_class
-
- topic = Topic.find(1)
- assert_equal topic.title, topic_observer.topic.title
- end
-
- def test_observing_two_classes
- multi_observer = MultiObserver.instance
-
- topic = Topic.find(1)
- assert_equal topic.title, multi_observer.record.title
-
- developer = Developer.find(1)
- assert_equal developer.name, multi_observer.record.name
- end
-
- def test_observing_subclasses
- multi_observer = MultiObserver.instance
-
- developer = SpecialDeveloper.find(1)
- assert_equal developer.name, multi_observer.record.name
-
- klass = Class.new(Developer)
- assert_equal klass, multi_observer.last_inherited
-
- developer = klass.find(1)
- assert_equal developer.name, multi_observer.record.name
- end
-
- def test_after_find_can_be_observed_when_its_not_defined_on_the_model
- observer = MinimalisticObserver.instance
- assert_equal Minimalistic, MinimalisticObserver.observed_class
-
- minimalistic = Minimalistic.find(1)
- assert_equal minimalistic, observer.minimalistic
- end
-
- def test_after_find_can_be_observed_when_its_defined_on_the_model
- observer = TopicObserver.instance
- assert_equal Topic, TopicObserver.observed_class
-
- topic = Topic.find(1)
- assert_equal topic, observer.topic
- end
-
- def test_invalid_observer
- assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
- end
-
- test "model callbacks fire before observers are notified" do
- callers = []
-
- comment = ValidatedComment.new
- comment.callers = ValidatedCommentObserver.instance.callers = callers
-
- comment.valid?
- assert_equal [ValidatedComment, ValidatedComment, ValidatedCommentObserver], callers,
- "model callbacks did not fire before observers were notified"
- end
-
- test "able to save developer" do
- SalaryChecker.instance # activate
- developer = SpecialDeveloper.new :name => 'Roger', :salary => 100000
- assert developer.save, "developer with normal salary failed to save"
- end
-
- test "unable to save developer with low salary" do
- SalaryChecker.instance # activate
- developer = SpecialDeveloper.new :name => 'Rookie', :salary => 50000
- assert !developer.save, "allowed to save a developer with too low salary"
- end
-
- test "able to call methods defined with included module" do # https://rails.lighthouseapp.com/projects/8994/tickets/6065-activerecordobserver-is-not-aware-of-method-added-by-including-modules
- SalaryChecker.instance # activate
- developer = SpecialDeveloper.create! :name => 'Roger', :salary => 100000
- assert_equal developer, SalaryChecker.instance.last_saved
- end
-
- test "around filter from observer should accept block" do
- observer = AroundTopicObserver.instance
- topic = AroundTopic.new
- topic.save
- assert_nil observer.topic_ids.first
- assert_not_nil observer.topic_ids.last
- end
-
- test "able to disable observers" do
- observer = DeveloperObserver.instance # activate
- observer.calls.clear
-
- ActiveRecord::Base.observers.disable DeveloperObserver do
- Developer.create! :name => 'Ancestor', :salary => 100000
- SpecialDeveloper.create! :name => 'Descendent', :salary => 100000
- end
-
- assert_equal [], observer.calls
- end
-
- def test_observer_is_called_once
- observer = DeveloperObserver.instance # activate
- observer.calls.clear
-
- developer = Developer.create! :name => 'Ancestor', :salary => 100000
- special_developer = SpecialDeveloper.create! :name => 'Descendent', :salary => 100000
-
- assert_equal [developer, special_developer], observer.calls
- end
-
-end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 2392516395..a0a3e6cb0d 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -207,7 +207,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
s.reload
assert_equal "unchangeable name", s.name
- s.update_attributes(:name => "changed name")
+ s.update(name: "changed name")
s.reload
assert_equal "unchangeable name", s.name
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 70d00aecf9..345e83a102 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "models/binary"
require "models/developer"
require "models/post"
require "active_support/log_subscriber/test_helper"
@@ -100,4 +101,12 @@ class LogSubscriberTest < ActiveRecord::TestCase
def test_initializes_runtime
Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
end
+
+ def test_binary_data_is_not_logged
+ skip if current_adapter?(:Mysql2Adapter)
+
+ Binary.create(:data => 'some binary data')
+ wait
+ assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join)
+ end
end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 86451289e7..5ac4a16f33 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -50,7 +50,7 @@ module ActiveRecord
def test_create_table_with_defaults
# MySQL doesn't allow defaults on TEXT or BLOB columns.
- mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
+ mysql = current_adapter?(:MysqlAdapter, :Mysql2Adapter)
connection.create_table :testings do |t|
t.column :one, :string, :default => "hello"
@@ -99,7 +99,7 @@ module ActiveRecord
assert_equal 'smallint', one.sql_type
assert_equal 'integer', four.sql_type
assert_equal 'bigint', eight.sql_type
- elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
assert_match 'int(11)', default.sql_type
assert_match 'tinyint', one.sql_type
assert_match 'int', four.sql_type
@@ -293,7 +293,7 @@ module ActiveRecord
end
assert connection.column_exists?(:testings, :foo)
- refute connection.column_exists?(:testings, :bar)
+ assert_not connection.column_exists?(:testings, :bar)
end
def test_column_exists_with_type
@@ -303,10 +303,10 @@ module ActiveRecord
end
assert connection.column_exists?(:testings, :foo, :string)
- refute connection.column_exists?(:testings, :foo, :integer)
+ assert_not connection.column_exists?(:testings, :foo, :integer)
assert connection.column_exists?(:testings, :bar, :decimal)
- refute connection.column_exists?(:testings, :bar, :integer)
+ assert_not connection.column_exists?(:testings, :bar, :integer)
end
def test_column_exists_with_definition
@@ -318,13 +318,13 @@ module ActiveRecord
end
assert connection.column_exists?(:testings, :foo, :string, limit: 100)
- refute connection.column_exists?(:testings, :foo, :string, limit: nil)
+ assert_not connection.column_exists?(:testings, :foo, :string, limit: nil)
assert connection.column_exists?(:testings, :bar, :decimal, precision: 8, scale: 2)
- refute connection.column_exists?(:testings, :bar, :decimal, precision: nil, scale: nil)
+ assert_not connection.column_exists?(:testings, :bar, :decimal, precision: nil, scale: nil)
assert connection.column_exists?(:testings, :taggable_id, :integer, null: false)
- refute connection.column_exists?(:testings, :taggable_id, :integer, null: true)
+ assert_not connection.column_exists?(:testings, :taggable_id, :integer, null: true)
assert connection.column_exists?(:testings, :taggable_type, :string, default: 'Photo')
- refute connection.column_exists?(:testings, :taggable_type, :string, default: nil)
+ assert_not connection.column_exists?(:testings, :taggable_type, :string, default: nil)
end
def test_column_exists_on_table_with_no_options_parameter_supplied
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 4614be9650..8065541bfe 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -1,23 +1,11 @@
require "cases/migration/helper"
+require "minitest/mock"
module ActiveRecord
class Migration
class TableTest < ActiveRecord::TestCase
- class MockConnection < MiniTest::Mock
- def native_database_types
- {
- :string => 'varchar(255)',
- :integer => 'integer',
- }
- end
-
- def type_to_sql(type, limit, precision, scale)
- native_database_types[type]
- end
- end
-
def setup
- @connection = MockConnection.new
+ @connection = MiniTest::Mock.new
end
def teardown
@@ -98,26 +86,18 @@ module ActiveRecord
end
end
- def string_column
- @connection.native_database_types[:string]
- end
-
- def integer_column
- @connection.native_database_types[:integer]
- end
-
def test_integer_creates_integer_column
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, :foo, integer_column, {}]
- @connection.expect :add_column, nil, [:delete_me, :bar, integer_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :foo, :integer, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
t.integer :foo, :bar
end
end
def test_string_creates_string_column
with_change_table do |t|
- @connection.expect :add_column, nil, [:delete_me, :foo, string_column, {}]
- @connection.expect :add_column, nil, [:delete_me, :bar, string_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :foo, :string, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, :string, {}]
t.string :foo, :bar
end
end
@@ -164,6 +144,13 @@ module ActiveRecord
end
end
+ def test_rename_index_renames_index
+ with_change_table do |t|
+ @connection.expect :rename_index, nil, [:delete_me, :bar, :baz]
+ t.rename_index :bar, :baz
+ end
+ end
+
def test_change_changes_column
with_change_table do |t|
@connection.expect :change_column, nil, [:delete_me, :bar, :string, {}]
@@ -187,14 +174,14 @@ module ActiveRecord
def test_remove_drops_single_column
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, :bar]
+ @connection.expect :remove_columns, nil, [:delete_me, :bar]
t.remove :bar
end
end
def test_remove_drops_multiple_columns
with_change_table do |t|
- @connection.expect :remove_column, nil, [:delete_me, :bar, :baz]
+ @connection.expect :remove_columns, nil, [:delete_me, :bar, :baz]
t.remove :bar, :baz
end
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index b88db384a0..ec2926632c 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -16,7 +16,7 @@ module ActiveRecord
end
def test_add_remove_single_field_using_string_arguments
- refute TestModel.column_methods_hash.key?(:last_name)
+ assert_not TestModel.column_methods_hash.key?(:last_name)
add_column 'test_models', 'last_name', :string
@@ -27,11 +27,11 @@ module ActiveRecord
remove_column 'test_models', 'last_name'
TestModel.reset_column_information
- refute TestModel.column_methods_hash.key?(:last_name)
+ assert_not TestModel.column_methods_hash.key?(:last_name)
end
def test_add_remove_single_field_using_symbol_arguments
- refute TestModel.column_methods_hash.key?(:last_name)
+ assert_not TestModel.column_methods_hash.key?(:last_name)
add_column :test_models, :last_name, :string
@@ -41,7 +41,7 @@ module ActiveRecord
remove_column :test_models, :last_name
TestModel.reset_column_information
- refute TestModel.column_methods_hash.key?(:last_name)
+ assert_not TestModel.column_methods_hash.key?(:last_name)
end
def test_unabstracted_database_dependent_types
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index f2213ee6aa..2cad8a6d96 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -26,7 +26,7 @@ module ActiveRecord
}.new)
assert recorder.respond_to?(:create_table), 'respond_to? create_table'
recorder.send(:create_table, :horses)
- assert_equal [[:create_table, [:horses]]], recorder.commands
+ assert_equal [[:create_table, [:horses], nil]], recorder.commands
end
def test_unknown_commands_delegate
@@ -34,10 +34,15 @@ module ActiveRecord
assert_equal 'bar', recorder.foo
end
- def test_unknown_commands_raise_exception_if_they_cannot_delegate
- @recorder.record :execute, ['some sql']
+ def test_inverse_of_raise_exception_on_unknown_commands
assert_raises(ActiveRecord::IrreversibleMigration) do
- @recorder.inverse
+ @recorder.inverse_of :execute, ['some sql']
+ end
+ end
+
+ def test_irreversible_commands_raise_exception
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.revert{ @recorder.execute 'some sql' }
end
end
@@ -46,121 +51,196 @@ module ActiveRecord
assert_equal 1, @recorder.commands.length
end
- def test_inverse
- @recorder.record :create_table, [:system_settings]
- assert_equal 1, @recorder.inverse.length
-
- @recorder.record :rename_table, [:old, :new]
- assert_equal 2, @recorder.inverse.length
+ def test_inverted_commands_are_reversed
+ @recorder.revert do
+ @recorder.record :create_table, [:hello]
+ @recorder.record :create_table, [:world]
+ end
+ tables = @recorder.commands.map{|_cmd, args, _block| args}
+ assert_equal [[:world], [:hello]], tables
end
- def test_inverted_commands_are_reveresed
- @recorder.record :create_table, [:hello]
- @recorder.record :create_table, [:world]
- tables = @recorder.inverse.map(&:last)
- assert_equal [[:world], [:hello]], tables
+ def test_revert_order
+ block = Proc.new{|t| t.string :name }
+ @recorder.instance_eval do
+ create_table("apples", &block)
+ revert do
+ create_table("bananas", &block)
+ revert do
+ create_table("clementines", &block)
+ create_table("dates")
+ end
+ create_table("elderberries")
+ end
+ revert do
+ create_table("figs", &block)
+ create_table("grapes")
+ end
+ end
+ assert_equal [[:create_table, ["apples"], block], [:drop_table, ["elderberries"], nil],
+ [:create_table, ["clementines"], block], [:create_table, ["dates"], nil],
+ [:drop_table, ["bananas"], block], [:drop_table, ["grapes"], nil],
+ [:drop_table, ["figs"], block]], @recorder.commands
+ end
+
+ def test_invert_change_table
+ @recorder.revert do
+ @recorder.change_table :fruits do |t|
+ t.string :name
+ t.rename :kind, :cultivar
+ end
+ end
+ assert_equal [
+ [:rename_column, [:fruits, :cultivar, :kind]],
+ [:remove_column, [:fruits, :name, :string, {}], nil],
+ ], @recorder.commands
+
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.revert do
+ @recorder.change_table :fruits do |t|
+ t.remove :kind
+ end
+ end
+ end
end
def test_invert_create_table
- @recorder.record :create_table, [:system_settings]
- drop_table = @recorder.inverse.first
- assert_equal [:drop_table, [:system_settings]], drop_table
+ @recorder.revert do
+ @recorder.record :create_table, [:system_settings]
+ end
+ drop_table = @recorder.commands.first
+ assert_equal [:drop_table, [:system_settings], nil], drop_table
+ end
+
+ def test_invert_create_table_with_options_and_block
+ block = Proc.new{}
+ drop_table = @recorder.inverse_of :create_table, [:people_reminders, id: false], &block
+ assert_equal [:drop_table, [:people_reminders, id: false], block], drop_table
+ end
+
+ def test_invert_drop_table
+ block = Proc.new{}
+ create_table = @recorder.inverse_of :drop_table, [:people_reminders, id: false], &block
+ assert_equal [:create_table, [:people_reminders, id: false], block], create_table
end
- def test_invert_create_table_with_options
- @recorder.record :create_table, [:people_reminders, {:id => false}]
- drop_table = @recorder.inverse.first
- assert_equal [:drop_table, [:people_reminders]], drop_table
+ def test_invert_drop_table_without_a_block_nor_option
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.inverse_of :drop_table, [:people_reminders]
+ end
end
def test_invert_create_join_table
- @recorder.record :create_join_table, [:musics, :artists]
- drop_table = @recorder.inverse.first
- assert_equal [:drop_table, [:artists_musics]], drop_table
+ drop_join_table = @recorder.inverse_of :create_join_table, [:musics, :artists]
+ assert_equal [:drop_join_table, [:musics, :artists], nil], drop_join_table
end
def test_invert_create_join_table_with_table_name
- @recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}]
- drop_table = @recorder.inverse.first
- assert_equal [:drop_table, [:catalog]], drop_table
+ drop_join_table = @recorder.inverse_of :create_join_table, [:musics, :artists, table_name: :catalog]
+ assert_equal [:drop_join_table, [:musics, :artists, table_name: :catalog], nil], drop_join_table
+ end
+
+ def test_invert_drop_join_table
+ block = Proc.new{}
+ create_join_table = @recorder.inverse_of :drop_join_table, [:musics, :artists, table_name: :catalog], &block
+ assert_equal [:create_join_table, [:musics, :artists, table_name: :catalog], block], create_join_table
end
def test_invert_rename_table
- @recorder.record :rename_table, [:old, :new]
- rename = @recorder.inverse.first
+ rename = @recorder.inverse_of :rename_table, [:old, :new]
assert_equal [:rename_table, [:new, :old]], rename
end
def test_invert_add_column
- @recorder.record :add_column, [:table, :column, :type, {}]
- remove = @recorder.inverse.first
- assert_equal [:remove_column, [:table, :column]], remove
+ remove = @recorder.inverse_of :add_column, [:table, :column, :type, {}]
+ assert_equal [:remove_column, [:table, :column, :type, {}], nil], remove
+ end
+
+ def test_invert_remove_column
+ add = @recorder.inverse_of :remove_column, [:table, :column, :type, {}]
+ assert_equal [:add_column, [:table, :column, :type, {}], nil], add
+ end
+
+ def test_invert_remove_column_without_type
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.inverse_of :remove_column, [:table, :column]
+ end
end
def test_invert_rename_column
- @recorder.record :rename_column, [:table, :old, :new]
- rename = @recorder.inverse.first
+ rename = @recorder.inverse_of :rename_column, [:table, :old, :new]
assert_equal [:rename_column, [:table, :new, :old]], rename
end
def test_invert_add_index
- @recorder.record :add_index, [:table, [:one, :two], {:options => true}]
- remove = @recorder.inverse.first
- assert_equal [:remove_index, [:table, {:column => [:one, :two]}]], remove
+ remove = @recorder.inverse_of :add_index, [:table, [:one, :two], options: true]
+ assert_equal [:remove_index, [:table, {column: [:one, :two], options: true}]], remove
end
def test_invert_add_index_with_name
- @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}]
- remove = @recorder.inverse.first
- assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove
+ remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"]
+ assert_equal [:remove_index, [:table, {column: [:one, :two], name: "new_index"}]], remove
end
def test_invert_add_index_with_no_options
- @recorder.record :add_index, [:table, [:one, :two]]
- remove = @recorder.inverse.first
- assert_equal [:remove_index, [:table, {:column => [:one, :two]}]], remove
+ remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
+ assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove
+ end
+
+ def test_invert_remove_index
+ add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], options: true}]
+ assert_equal [:add_index, [:table, [:one, :two], options: true]], add
+ end
+
+ def test_invert_remove_index_with_name
+ add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], name: "new_index"}]
+ assert_equal [:add_index, [:table, [:one, :two], name: "new_index"]], add
+ end
+
+ def test_invert_remove_index_with_no_special_options
+ add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two]}]
+ assert_equal [:add_index, [:table, [:one, :two], {}]], add
+ end
+
+ def test_invert_remove_index_with_no_column
+ assert_raises(ActiveRecord::IrreversibleMigration) do
+ @recorder.inverse_of :remove_index, [:table, name: "new_index"]
+ end
end
def test_invert_rename_index
- @recorder.record :rename_index, [:table, :old, :new]
- rename = @recorder.inverse.first
+ rename = @recorder.inverse_of :rename_index, [:table, :old, :new]
assert_equal [:rename_index, [:table, :new, :old]], rename
end
def test_invert_add_timestamps
- @recorder.record :add_timestamps, [:table]
- remove = @recorder.inverse.first
- assert_equal [:remove_timestamps, [:table]], remove
+ remove = @recorder.inverse_of :add_timestamps, [:table]
+ assert_equal [:remove_timestamps, [:table], nil], remove
end
def test_invert_remove_timestamps
- @recorder.record :remove_timestamps, [:table]
- add = @recorder.inverse.first
- assert_equal [:add_timestamps, [:table]], add
+ add = @recorder.inverse_of :remove_timestamps, [:table]
+ assert_equal [:add_timestamps, [:table], nil], add
end
def test_invert_add_reference
- @recorder.record :add_reference, [:table, :taggable, { polymorphic: true }]
- remove = @recorder.inverse.first
- assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }]], remove
+ remove = @recorder.inverse_of :add_reference, [:table, :taggable, { polymorphic: true }]
+ assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }], nil], remove
end
def test_invert_add_belongs_to_alias
- @recorder.record :add_belongs_to, [:table, :user]
- remove = @recorder.inverse.first
- assert_equal [:remove_reference, [:table, :user]], remove
+ remove = @recorder.inverse_of :add_belongs_to, [:table, :user]
+ assert_equal [:remove_reference, [:table, :user], nil], remove
end
def test_invert_remove_reference
- @recorder.record :remove_reference, [:table, :taggable, { polymorphic: true }]
- add = @recorder.inverse.first
- assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }]], add
+ add = @recorder.inverse_of :remove_reference, [:table, :taggable, { polymorphic: true }]
+ assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }], nil], add
end
def test_invert_remove_belongs_to_alias
- @recorder.record :remove_belongs_to, [:table, :user]
- add = @recorder.inverse.first
- assert_equal [:add_reference, [:table, :user]], add
+ add = @recorder.inverse_of :remove_belongs_to, [:table, :user]
+ assert_equal [:add_reference, [:table, :user], nil], add
end
end
end
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index cd1b0e8b47..efaec0f823 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -35,6 +35,12 @@ module ActiveRecord
assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
end
+ def test_create_join_table_with_symbol_and_string
+ connection.create_join_table :artists, 'musics'
+
+ assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
+ end
+
def test_create_join_table_with_the_proper_order
connection.create_join_table :videos, :musics
@@ -72,6 +78,48 @@ module ActiveRecord
assert_equal [%w(artist_id music_id)], connection.indexes(:artists_musics).map(&:columns)
end
+
+ def test_drop_join_table
+ connection.create_join_table :artists, :musics
+ connection.drop_join_table :artists, :musics
+
+ assert !connection.tables.include?('artists_musics')
+ end
+
+ def test_drop_join_table_with_strings
+ connection.create_join_table :artists, :musics
+ connection.drop_join_table 'artists', 'musics'
+
+ assert !connection.tables.include?('artists_musics')
+ end
+
+ def test_drop_join_table_with_the_proper_order
+ connection.create_join_table :videos, :musics
+ connection.drop_join_table :videos, :musics
+
+ assert !connection.tables.include?('musics_videos')
+ end
+
+ def test_drop_join_table_with_the_table_name
+ connection.create_join_table :artists, :musics, table_name: :catalog
+ connection.drop_join_table :artists, :musics, table_name: :catalog
+
+ assert !connection.tables.include?('catalog')
+ end
+
+ def test_drop_join_table_with_the_table_name_as_string
+ connection.create_join_table :artists, :musics, table_name: 'catalog'
+ connection.drop_join_table :artists, :musics, table_name: 'catalog'
+
+ assert !connection.tables.include?('catalog')
+ end
+
+ def test_drop_join_table_with_column_options
+ connection.create_join_table :artists, :musics, column_options: {null: true}
+ connection.drop_join_table :artists, :musics, column_options: {null: true}
+
+ assert !connection.tables.include?('artists_musics')
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
index 768ebc5861..e28feedcf9 100644
--- a/activerecord/test/cases/migration/helper.rb
+++ b/activerecord/test/cases/migration/helper.rb
@@ -2,12 +2,10 @@ require "cases/helper"
module ActiveRecord
class Migration
- class << self
- attr_accessor :message_count
- end
+ class << self; attr_accessor :message_count; end
+ self.message_count = 0
def puts(text="")
- ActiveRecord::Migration.message_count ||= 0
ActiveRecord::Migration.message_count += 1
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 0787414d8f..a41f2c10f0 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -35,7 +35,7 @@ module ActiveRecord
connection.rename_index(table_name, 'old_idx', 'new_idx')
# if the adapter doesn't support the indexes call, pick defaults that let the test pass
- refute connection.index_name_exists?(table_name, 'old_idx', false)
+ assert_not connection.index_name_exists?(table_name, 'old_idx', false)
assert connection.index_name_exists?(table_name, 'new_idx', true)
end
@@ -63,7 +63,7 @@ module ActiveRecord
connection.add_index(table_name, "foo", :name => too_long_index_name)
}
- refute connection.index_name_exists?(table_name, too_long_index_name, false)
+ assert_not connection.index_name_exists?(table_name, too_long_index_name, false)
connection.add_index(table_name, "foo", :name => good_index_name)
assert connection.index_name_exists?(table_name, good_index_name, false)
@@ -75,7 +75,7 @@ module ActiveRecord
assert connection.index_exists?(table_name, :foo, :name => :symbol_index_name)
connection.remove_index table_name, :name => :symbol_index_name
- refute connection.index_exists?(table_name, :foo, :name => :symbol_index_name)
+ assert_not connection.index_exists?(table_name, :foo, :name => :symbol_index_name)
end
def test_index_exists
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
index 264a99f9ce..3ff89524fe 100644
--- a/activerecord/test/cases/migration/references_index_test.rb
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -29,7 +29,7 @@ module ActiveRecord
t.references :foo
end
- refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index_explicit
@@ -37,7 +37,7 @@ module ActiveRecord
t.references :foo, :index => false
end
- refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_creates_index_with_options
@@ -75,7 +75,7 @@ module ActiveRecord
t.references :foo
end
- refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_does_not_create_index_for_existing_table_explicit
@@ -84,7 +84,7 @@ module ActiveRecord
t.references :foo, :index => false
end
- refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ assert_not connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
end
def test_creates_polymorphic_index_for_existing_table
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index d8a6565d54..e9545f2cce 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -22,7 +22,7 @@ module ActiveRecord
def test_does_not_create_reference_type_column
add_reference table_name, :taggable
- refute column_exists?(table_name, :taggable_type, :string)
+ assert_not column_exists?(table_name, :taggable_type, :string)
end
def test_creates_reference_type_column
@@ -37,7 +37,7 @@ module ActiveRecord
def test_does_not_create_reference_id_index
add_reference table_name, :user
- refute index_exists?(table_name, :user_id)
+ assert_not index_exists?(table_name, :user_id)
end
def test_creates_polymorphic_index
@@ -57,19 +57,19 @@ module ActiveRecord
def test_deletes_reference_id_column
remove_reference table_name, :supplier
- refute column_exists?(table_name, :supplier_id, :integer)
+ assert_not column_exists?(table_name, :supplier_id, :integer)
end
def test_deletes_reference_id_index
remove_reference table_name, :supplier
- refute index_exists?(table_name, :supplier_id)
+ assert_not index_exists?(table_name, :supplier_id)
end
def test_does_not_delete_reference_type_column
with_polymorphic_column do
remove_reference table_name, :supplier
- refute column_exists?(table_name, :supplier_id, :integer)
+ assert_not column_exists?(table_name, :supplier_id, :integer)
assert column_exists?(table_name, :supplier_type, :string)
end
end
@@ -77,14 +77,14 @@ module ActiveRecord
def test_deletes_reference_type_column
with_polymorphic_column do
remove_reference table_name, :supplier, polymorphic: true
- refute column_exists?(table_name, :supplier_type, :string)
+ assert_not column_exists?(table_name, :supplier_type, :string)
end
end
def test_deletes_polymorphic_index
with_polymorphic_column do
remove_reference table_name, :supplier, polymorphic: true
- refute index_exists?(table_name, [:supplier_id, :supplier_type])
+ assert_not index_exists?(table_name, [:supplier_id, :supplier_type])
end
end
@@ -95,7 +95,7 @@ module ActiveRecord
def test_remove_belongs_to_alias
remove_belongs_to table_name, :supplier
- refute column_exists?(table_name, :supplier_id, :integer)
+ assert_not column_exists?(table_name, :supplier_id, :integer)
end
private
diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb
index d1a85ee5e4..8f6918d06a 100644
--- a/activerecord/test/cases/migration/rename_column_test.rb
+++ b/activerecord/test/cases/migration/rename_column_test.rb
@@ -84,16 +84,19 @@ module ActiveRecord
add_column "test_models", :hat_name, :string
add_index :test_models, :hat_name
- # FIXME: we should test that the index goes away
+ assert_equal 1, connection.indexes('test_models').size
rename_column "test_models", "hat_name", "name"
+ # FIXME: should we rename the index if it's name was autogenerated by rails?
+ assert_equal ['index_test_models_on_hat_name'], connection.indexes('test_models').map(&:name)
end
def test_remove_column_with_index
add_column "test_models", :hat_name, :string
add_index :test_models, :hat_name
- # FIXME: we should test that the index goes away
+ assert_equal 1, connection.indexes('test_models').size
remove_column("test_models", "hat_name")
+ assert_equal 0, connection.indexes('test_models').size
end
def test_remove_column_with_multi_column_index
@@ -101,14 +104,25 @@ module ActiveRecord
add_column "test_models", :hat_style, :string, :limit => 100
add_index "test_models", ["hat_style", "hat_size"], :unique => true
- # FIXME: we should test that the index goes away
+ assert_equal 1, connection.indexes('test_models').size
remove_column("test_models", "hat_size")
+
+ # Every database and/or database adapter has their own behavior
+ # if it drops the multi-column index when any of the indexed columns dropped by remove_column.
+ if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
+ assert_equal [], connection.indexes('test_models').map(&:name)
+ else
+ assert_equal ['index_test_models_on_hat_style_and_hat_size'], connection.indexes('test_models').map(&:name)
+ end
end
- # FIXME: we need to test that these calls do something
def test_change_type_of_not_null_column
change_column "test_models", "updated_at", :datetime, :null => false
change_column "test_models", "updated_at", :datetime, :null => false
+
+ TestModel.reset_column_information
+ assert_equal false, TestModel.columns_hash['updated_at'].null
+ ensure
change_column "test_models", "updated_at", :datetime, :null => true
end
@@ -119,7 +133,7 @@ module ActiveRecord
change_column "test_models", "funny", :boolean, :null => false, :default => true
TestModel.reset_column_information
- refute TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
+ assert_not TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
change_column "test_models", "funny", :boolean, :null => true
TestModel.reset_column_information
@@ -138,7 +152,7 @@ module ActiveRecord
new_columns = connection.columns(TestModel.table_name)
- refute new_columns.find { |c| c.name == 'age' and c.type == :integer }
+ assert_not new_columns.find { |c| c.name == 'age' and c.type == :integer }
assert new_columns.find { |c| c.name == 'age' and c.type == :string }
old_columns = connection.columns(TestModel.table_name)
@@ -149,7 +163,7 @@ module ActiveRecord
change_column :test_models, :approved, :boolean, :default => false
new_columns = connection.columns(TestModel.table_name)
- refute new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
+ assert_not new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
change_column :test_models, :approved, :boolean, :default => true
end
@@ -160,7 +174,7 @@ module ActiveRecord
change_column "test_models", "contributor", :boolean, :default => nil
TestModel.reset_column_information
- refute TestModel.new.contributor?
+ assert_not TestModel.new.contributor?
assert_nil TestModel.new.contributor
end
@@ -170,7 +184,17 @@ module ActiveRecord
change_column "test_models", "administrator", :boolean, :default => false
TestModel.reset_column_information
- refute TestModel.new.administrator?
+ assert_not TestModel.new.administrator?
+ end
+
+ def test_change_column_with_custom_index_name
+ add_column "test_models", "category", :string
+ add_index :test_models, :category, name: 'test_models_categories_idx'
+
+ assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name)
+ change_column "test_models", "category", :string, null: false, default: 'article'
+
+ assert_equal ['test_models_categories_idx'], connection.indexes('test_models').map(&:name)
end
def test_change_column_default
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index c155f29973..9cb64a6a71 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -232,7 +232,7 @@ class MigrationTest < ActiveRecord::TestCase
skip "not supported on #{ActiveRecord::Base.connection.class}"
end
- refute Person.column_methods_hash.include?(:last_name)
+ assert_not Person.column_methods_hash.include?(:last_name)
migration = Struct.new(:name, :version) {
def migrate(x); raise 'Something broke'; end
@@ -245,7 +245,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
Person.reset_column_information
- refute Person.column_methods_hash.include?(:last_name)
+ assert_not Person.column_methods_hash.include?(:last_name)
end
def test_schema_migrations_table_name
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 1e16addcf3..e905006570 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -84,6 +84,12 @@ module ActiveRecord
end
end
+ def test_finds_migrations_in_numbered_directory
+ migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + '/10_urban']
+ assert_equal 9, migrations[0].version
+ assert_equal 'AddExpressions', migrations[0].name
+ end
+
def test_deprecated_constructor
assert_deprecated do
ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid")
@@ -115,11 +121,11 @@ module ActiveRecord
ActiveRecord::Migrator.new(:up, pass_one).migrate
assert pass_one.first.went_up
- refute pass_one.first.went_down
+ assert_not pass_one.first.went_down
pass_two = [Sensor.new('One', 1), Sensor.new('Three', 3)]
ActiveRecord::Migrator.new(:up, pass_two).migrate
- refute pass_two[0].went_up
+ assert_not pass_two[0].went_up
assert pass_two[1].went_up
assert pass_two.all? { |x| !x.went_down }
@@ -129,7 +135,7 @@ module ActiveRecord
ActiveRecord::Migrator.new(:down, pass_three).migrate
assert pass_three[0].went_down
- refute pass_three[1].went_down
+ assert_not pass_three[1].went_down
assert pass_three[2].went_down
end
@@ -301,7 +307,7 @@ module ActiveRecord
_, migrator = migrator_class(3)
ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations")
- refute ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ assert_not ActiveRecord::Base.connection.table_exists?('schema_migrations')
migrator.migrate("valid", 1)
assert ActiveRecord::Base.connection.table_exists?('schema_migrations')
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 3f08f9ea4d..9574678e38 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -79,10 +79,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_should_disable_allow_destroy_by_default
Pirate.accepts_nested_attributes_for :ship
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
- ship = pirate.create_ship(:name => 'Nights Dirty Lightning')
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ ship = pirate.create_ship(name: 'Nights Dirty Lightning')
- pirate.update_attributes(:ship_attributes => { '_destroy' => true, :id => ship.id })
+ pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id })
assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload }
end
@@ -125,33 +125,33 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
def test_reject_if_with_a_proc_which_returns_true_always_for_has_one
Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| true }
- pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
- ship = pirate.create_ship(:name => 's1')
- pirate.update_attributes({:ship_attributes => { :name => 's2', :id => ship.id } })
+ pirate = Pirate.new(catchphrase: "Stop wastin' me time")
+ ship = pirate.create_ship(name: 's1')
+ pirate.update({ship_attributes: { name: 's2', id: ship.id } })
assert_equal 's1', ship.reload.name
end
def test_reject_if_with_a_proc_which_returns_true_always_for_has_many
Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }
- man = Man.create(:name => "John")
- interest = man.interests.create(:topic => 'photography')
- man.update_attributes({:interests_attributes => { :topic => 'gardening', :id => interest.id } })
+ man = Man.create(name: "John")
+ interest = man.interests.create(topic: 'photography')
+ man.update({interests_attributes: { topic: 'gardening', id: interest.id } })
assert_equal 'photography', interest.reload.topic
end
def test_destroy_works_independent_of_reject_if
Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true }, :allow_destroy => true
- man = Man.create(:name => "Jon")
- interest = man.interests.create(:topic => 'the ladies')
- man.update_attributes({:interests_attributes => { :_destroy => "1", :id => interest.id } })
+ man = Man.create(name: "Jon")
+ interest = man.interests.create(topic: 'the ladies')
+ man.update({interests_attributes: { _destroy: "1", id: interest.id } })
assert man.reload.interests.empty?
end
def test_has_many_association_updating_a_single_record
Man.accepts_nested_attributes_for(:interests)
- man = Man.create(:name => 'John')
- interest = man.interests.create(:topic => 'photography')
- man.update_attributes({:interests_attributes => {:topic => 'gardening', :id => interest.id}})
+ man = Man.create(name: 'John')
+ interest = man.interests.create(topic: 'photography')
+ man.update({interests_attributes: {topic: 'gardening', id: interest.id}})
assert_equal 'gardening', interest.reload.topic
end
@@ -284,8 +284,8 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@pirate.ship.destroy
[1, '1', true, 'true'].each do |truth|
- ship = @pirate.reload.create_ship(:name => 'Mister Pablo')
- @pirate.update_attributes(:ship_attributes => { :id => ship.id, :_destroy => truth })
+ ship = @pirate.reload.create_ship(name: 'Mister Pablo')
+ @pirate.update(ship_attributes: { id: ship.id, _destroy: truth })
assert_nil @pirate.reload.ship
assert_raise(ActiveRecord::RecordNotFound) { Ship.find(ship.id) }
@@ -294,7 +294,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
[nil, '0', 0, 'false', false].each do |not_truth|
- @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => not_truth })
+ @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: not_truth })
assert_equal @ship, @pirate.reload.ship
end
@@ -303,7 +303,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
- @pirate.update_attributes(:ship_attributes => { :id => @pirate.ship.id, :_destroy => '1' })
+ @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' })
assert_equal @ship, @pirate.reload.ship
@@ -317,8 +317,8 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
end
- def test_should_work_with_update_attributes_as_well
- @pirate.update_attributes({ :catchphrase => 'Arr', :ship_attributes => { :id => @ship.id, :name => 'Mister Pablo' } })
+ def test_should_work_with_update_as_well
+ @pirate.update({ catchphrase: 'Arr', ship_attributes: { id: @ship.id, name: 'Mister Pablo' } })
@pirate.reload
assert_equal 'Arr', @pirate.catchphrase
@@ -342,22 +342,22 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_accept_update_only_option
- @pirate.update_attributes(:update_only_ship_attributes => { :id => @pirate.ship.id, :name => 'Mayflower' })
+ @pirate.update(update_only_ship_attributes: { id: @pirate.ship.id, name: 'Mayflower' })
end
def test_should_create_new_model_when_nothing_is_there_and_update_only_is_true
@ship.delete
- @pirate.reload.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' })
+ @pirate.reload.update(update_only_ship_attributes: { name: 'Mayflower' })
assert_not_nil @pirate.ship
end
def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
@ship.delete
- @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
+ @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
- @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower' })
+ @pirate.update(update_only_ship_attributes: { name: 'Mayflower' })
assert_equal 'Mayflower', @ship.reload.name
assert_equal @ship, @pirate.reload.ship
@@ -365,9 +365,9 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_update_existing_when_update_only_is_true_and_id_is_given
@ship.delete
- @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
+ @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
- @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id })
+ @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id })
assert_equal 'Mayflower', @ship.reload.name
assert_equal @ship, @pirate.reload.ship
@@ -376,9 +376,9 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true
@ship.delete
- @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
+ @ship = @pirate.create_update_only_ship(name: 'Nights Dirty Lightning')
- @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id, :_destroy => true })
+ @pirate.update(update_only_ship_attributes: { name: 'Mayflower', id: @ship.id, _destroy: true })
assert_nil @pirate.reload.ship
assert_raise(ActiveRecord::RecordNotFound) { Ship.find(@ship.id) }
@@ -468,15 +468,15 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy
@ship.pirate.destroy
[1, '1', true, 'true'].each do |truth|
- pirate = @ship.reload.create_pirate(:catchphrase => 'Arr')
- @ship.update_attributes(:pirate_attributes => { :id => pirate.id, :_destroy => truth })
+ pirate = @ship.reload.create_pirate(catchphrase: 'Arr')
+ @ship.update(pirate_attributes: { id: pirate.id, _destroy: truth })
assert_raise(ActiveRecord::RecordNotFound) { pirate.reload }
end
end
def test_should_unset_association_when_an_existing_record_is_destroyed
original_pirate_id = @ship.pirate.id
- @ship.update_attributes! pirate_attributes: { id: @ship.pirate.id, _destroy: true }
+ @ship.update! pirate_attributes: { id: @ship.pirate.id, _destroy: true }
assert_empty Pirate.where(id: original_pirate_id)
assert_nil @ship.pirate_id
@@ -490,7 +490,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
[nil, '0', 0, 'false', false].each do |not_truth|
- @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => not_truth })
+ @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth })
assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
end
end
@@ -498,14 +498,14 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? }
- @ship.update_attributes(:pirate_attributes => { :id => @ship.pirate.id, :_destroy => '1' })
+ @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' })
assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
ensure
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
end
- def test_should_work_with_update_attributes_as_well
- @ship.update_attributes({ :name => 'Mister Pablo', :pirate_attributes => { :catchphrase => 'Arr' } })
+ def test_should_work_with_update_as_well
+ @ship.update({ name: 'Mister Pablo', pirate_attributes: { catchphrase: 'Arr' } })
@ship.reload
assert_equal 'Mister Pablo', @ship.name
@@ -534,18 +534,18 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
@pirate.delete
- @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
+ @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
- @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr' })
+ @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr' })
assert_equal 'Arr', @pirate.reload.catchphrase
assert_equal @pirate, @ship.reload.update_only_pirate
end
def test_should_update_existing_when_update_only_is_true_and_id_is_given
@pirate.delete
- @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
+ @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
- @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id })
+ @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id })
assert_equal 'Arr', @pirate.reload.catchphrase
assert_equal @pirate, @ship.reload.update_only_pirate
@@ -554,9 +554,9 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true
@pirate.delete
- @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
+ @pirate = @ship.create_update_only_pirate(catchphrase: 'Aye')
- @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id, :_destroy => true })
+ @ship.update(update_only_pirate_attributes: { catchphrase: 'Arr', id: @pirate.id, _destroy: true })
assert_raise(ActiveRecord::RecordNotFound) { @pirate.reload }
@@ -582,7 +582,7 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_take_a_hash_with_string_keys_and_assign_the_attributes_to_the_associated_models
@alternate_params[association_getter].stringify_keys!
- @pirate.update_attributes @alternate_params
+ @pirate.update @alternate_params
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
end
@@ -628,10 +628,10 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_refresh_saved_records_when_not_overwriting_unsaved_updates
@pirate.reload
- record = @pirate.class.reflect_on_association(@association_name).klass.new(:name => 'Grace OMalley')
+ record = @pirate.class.reflect_on_association(@association_name).klass.new(name: 'Grace OMalley')
@pirate.send(@association_name) << record
record.save!
- @pirate.send(@association_name).last.update_attributes!(:name => 'Polly')
+ @pirate.send(@association_name).last.update!(name: 'Polly')
assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
end
@@ -718,17 +718,17 @@ module NestedAttributesOnACollectionAssociationTests
end
end
- def test_should_work_with_update_attributes_as_well
- @pirate.update_attributes(:catchphrase => 'Arr',
+ def test_should_work_with_update_as_well
+ @pirate.update(catchphrase: 'Arr',
association_getter => { 'foo' => { :id => @child_1.id, :name => 'Grace OMalley' }})
assert_equal 'Grace OMalley', @child_1.reload.name
end
def test_should_update_existing_records_and_add_new_ones_that_have_no_id
- @alternate_params[association_getter]['baz'] = { :name => 'Buccaneers Servant' }
+ @alternate_params[association_getter]['baz'] = { name: 'Buccaneers Servant' }
assert_difference('@pirate.send(@association_name).count', +1) do
- @pirate.update_attributes @alternate_params
+ @pirate.update @alternate_params
end
assert_equal ['Grace OMalley', 'Privateers Greed', 'Buccaneers Servant'].to_set, @pirate.reload.send(@association_name).map(&:name).to_set
end
@@ -750,7 +750,7 @@ module NestedAttributesOnACollectionAssociationTests
[nil, '', '0', 0, 'false', false].each do |false_variable|
@alternate_params[association_getter]['foo']['_destroy'] = false_variable
assert_no_difference('@pirate.send(@association_name).count') do
- @pirate.update_attributes(@alternate_params)
+ @pirate.update(@alternate_params)
end
end
end
@@ -814,7 +814,7 @@ module NestedAttributesOnACollectionAssociationTests
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' }})
+ assert !man.update({interests_attributes: { id: interest.id, zine_id: 'foo' }})
end
end
@@ -945,18 +945,18 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
end
def test_should_update_existing_records_with_non_standard_primary_key
- @owner.update_attributes(@params)
+ @owner.update(@params)
assert_equal ['Foo', 'Bar'], @owner.pets.map(&:name)
end
- def test_attr_accessor_of_child_should_be_value_provided_during_update_attributes
+ def test_attr_accessor_of_child_should_be_value_provided_during_update
@owner = owners(:ashley)
@pet1 = pets(:chew)
attributes = {:pets_attributes => { "1"=> { :id => @pet1.id,
:name => "Foo2",
:current_user => "John",
:_destroy=>true }}}
- @owner.update_attributes(attributes)
+ @owner.update(attributes)
assert_equal 'John', Pet.after_destroy_output
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 4b938da5c4..b936cca875 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -16,7 +16,6 @@ require 'models/person'
require 'models/pet'
require 'models/toy'
require 'rexml/document'
-require 'active_support/core_ext/exception'
class PersistencesTest < ActiveRecord::TestCase
@@ -243,7 +242,7 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal "David", topic2.author_name
end
- def test_update
+ def test_update_object
topic = Topic.new
topic.title = "Another New Topic"
topic.written_on = "2003-12-12 23:23:00"
@@ -280,12 +279,23 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_sti_type
assert_instance_of Reply, topics(:second)
- topic = topics(:second).becomes(Topic)
+ topic = topics(:second).becomes!(Topic)
assert_instance_of Topic, topic
topic.save!
assert_instance_of Topic, Topic.find(topic.id)
end
+ def test_preserve_original_sti_type
+ reply = topics(:second)
+ assert_equal "Reply", reply.type
+
+ topic = reply.becomes(Topic)
+ assert_equal "Reply", reply.type
+
+ assert_instance_of Topic, topic
+ assert_equal "Reply", topic.type
+ end
+
def test_delete
topic = Topic.find(1)
assert_equal topic, topic.delete, 'topic.delete did not return self'
@@ -354,7 +364,7 @@ class PersistencesTest < ActiveRecord::TestCase
client.delete
assert client.frozen?
assert_kind_of Firm, client.firm
- assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
+ assert_raise(RuntimeError) { client.name = "something else" }
end
def test_destroy_new_record
@@ -368,7 +378,7 @@ class PersistencesTest < ActiveRecord::TestCase
client.destroy
assert client.frozen?
assert_kind_of Firm, client.firm
- assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
+ assert_raise(RuntimeError) { client.name = "something else" }
end
def test_update_attribute
@@ -381,7 +391,7 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_update_attribute_does_not_choke_on_nil
- assert Topic.find(1).update_attributes(nil)
+ assert Topic.find(1).update(nil)
end
def test_update_attribute_for_readonly_attribute
@@ -488,7 +498,7 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_column_with_one_changed_and_one_updated
t = Topic.order('id').limit(1).first
- title, author_name = t.title, t.author_name
+ author_name = t.author_name
t.author_name = 'John'
t.update_column(:title, 'super_title')
assert_equal 'John', t.author_name
@@ -501,6 +511,14 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal 'super_title', t.title
end
+ def test_update_column_with_default_scope
+ developer = DeveloperCalledDavid.first
+ developer.name = 'John'
+ developer.save!
+
+ assert developer.update_column(:name, 'Will'), 'did not update record due to default scope'
+ end
+
def test_update_columns
topic = Topic.find(1)
topic.update_columns({ "approved" => true, title: "Sebastian Topic" })
@@ -529,7 +547,7 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_columns_should_not_leave_the_object_dirty
topic = Topic.find(1)
- topic.update_attributes({ "content" => "Have a nice day", :author_name => "Jose" })
+ topic.update({ "content" => "Have a nice day", :author_name => "Jose" })
topic.reload
topic.update_columns({ content: "You too", "author_name" => "Sebastian" })
@@ -605,6 +623,30 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal true, topic.update_columns(title: "New title")
end
+ def test_update_columns_with_default_scope
+ developer = DeveloperCalledDavid.first
+ developer.name = 'John'
+ developer.save!
+
+ assert developer.update_columns(name: 'Will'), 'did not update record due to default scope'
+ end
+
+ def test_update
+ topic = Topic.find(1)
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+
+ topic.update("approved" => true, "title" => "The First Topic Updated")
+ topic.reload
+ assert topic.approved?
+ assert_equal "The First Topic Updated", topic.title
+
+ topic.update(approved: false, title: "The First Topic")
+ topic.reload
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+ end
+
def test_update_attributes
topic = Topic.find(1)
assert !topic.approved?
@@ -615,12 +657,33 @@ class PersistencesTest < ActiveRecord::TestCase
assert topic.approved?
assert_equal "The First Topic Updated", topic.title
- topic.update_attributes(:approved => false, :title => "The First Topic")
+ topic.update_attributes(approved: false, title: "The First Topic")
topic.reload
assert !topic.approved?
assert_equal "The First Topic", topic.title
end
+ def test_update!
+ Reply.validates_presence_of(:title)
+ reply = Reply.find(2)
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ reply.update!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening")
+ reply.reload
+ assert_equal "The Second Topic of the day updated", reply.title
+ assert_equal "Have a nice evening", reply.content
+
+ reply.update!(title: "The Second Topic of the day", content: "Have a nice day")
+ reply.reload
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ assert_raise(ActiveRecord::RecordInvalid) { reply.update!(title: nil, content: "Have a nice evening") }
+ ensure
+ Reply.reset_callbacks(:validate)
+ end
+
def test_update_attributes!
Reply.validates_presence_of(:title)
reply = Reply.find(2)
@@ -632,12 +695,12 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal "The Second Topic of the day updated", reply.title
assert_equal "Have a nice evening", reply.content
- reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day")
+ reply.update_attributes!(title: "The Second Topic of the day", content: "Have a nice day")
reply.reload
assert_equal "The Second Topic of the day", reply.title
assert_equal "Have a nice day", reply.content
- assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
+ assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(title: nil, content: "Have a nice evening") }
ensure
Reply.reset_callbacks(:validate)
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index bf8aacc363..8e5379cb1f 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -201,10 +201,10 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
end
end
-if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
-
+
def test_primaery_key_method_with_ansi_quotes
con = ActiveRecord::Base.connection
con.execute("SET SESSION sql_mode='ANSI_QUOTES'")
@@ -212,7 +212,7 @@ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
ensure
con.reconnect!
end
-
+
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 4ff481e6a5..136fda664c 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -167,7 +167,7 @@ class QueryCacheTest < ActiveRecord::TestCase
# 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) || current_adapter?(:Mysql2Adapter)
+ elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter)
# 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/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
new file mode 100644
index 0000000000..8ce44636b4
--- /dev/null
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -0,0 +1,75 @@
+require 'cases/helper'
+require 'models/post'
+require 'models/comment'
+
+module ActiveRecord
+ class WhereChainTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def test_not_eq
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello')
+ relation = Post.where.not(title: 'hello')
+ assert_equal([expected], relation.where_values)
+ end
+
+ def test_not_null
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], nil)
+ relation = Post.where.not(title: nil)
+ assert_equal([expected], relation.where_values)
+ end
+
+ def test_not_in
+ expected = Arel::Nodes::NotIn.new(Post.arel_table[:title], %w[hello goodbye])
+ relation = Post.where.not(title: %w[hello goodbye])
+ assert_equal([expected], relation.where_values)
+ end
+
+ def test_association_not_eq
+ expected = Arel::Nodes::NotEqual.new(Comment.arel_table[:title], 'hello')
+ relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
+ assert_equal(expected.to_sql, relation.where_values.first.to_sql)
+ end
+
+ def test_not_eq_with_preceding_where
+ relation = Post.where(title: 'hello').where.not(title: 'world')
+
+ expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'hello')
+ assert_equal(expected, relation.where_values.first)
+
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'world')
+ assert_equal(expected, relation.where_values.last)
+ end
+
+ def test_not_eq_with_succeeding_where
+ relation = Post.where.not(title: 'hello').where(title: 'world')
+
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'hello')
+ assert_equal(expected, relation.where_values.first)
+
+ expected = Arel::Nodes::Equality.new(Post.arel_table[:title], 'world')
+ assert_equal(expected, relation.where_values.last)
+ end
+
+ def test_not_eq_with_string_parameter
+ expected = Arel::Nodes::Not.new("title = 'hello'")
+ relation = Post.where.not("title = 'hello'")
+ assert_equal([expected], relation.where_values)
+ end
+
+ def test_not_eq_with_array_parameter
+ expected = Arel::Nodes::Not.new("title = 'hello'")
+ relation = Post.where.not(['title = ?', 'hello'])
+ assert_equal([expected], relation.where_values)
+ end
+
+ def test_chaining_multiple
+ relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
+
+ expected = Arel::Nodes::NotIn.new(Post.arel_table[:author_id], [1, 2])
+ assert_equal(expected, relation.where_values[0])
+
+ expected = Arel::Nodes::NotEqual.new(Post.arel_table[:title], 'ruby on rails')
+ assert_equal(expected, relation.where_values[1])
+ end
+ end
+end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 9c0b139dbf..297e865308 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -85,5 +85,11 @@ module ActiveRecord
def test_where_with_empty_hash_and_no_foreign_key
assert_equal 0, Edge.where(:sink => {}).count
end
+
+ def test_where_with_blank_conditions
+ [[], {}, nil, ""].each do |blank|
+ assert_equal 4, Edge.where(blank).order("sink_id").to_a.size
+ end
+ end
end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index d318dab1e1..78fb91d321 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -227,7 +227,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
def test_nested_exclusive_scope_for_create
comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
Comment.unscoped.create_with(:post_id => 1).scoping do
- assert_blank Comment.new.body
+ assert Comment.new.body.blank?
Comment.create :body => "Hey guys"
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 98e278df82..92dc575d37 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -6,26 +6,26 @@ module ActiveRecord
class RelationTest < ActiveRecord::TestCase
fixtures :posts, :comments
- class FakeKlass < Struct.new(:table_name)
+ class FakeKlass < Struct.new(:table_name, :name)
end
def test_construction
relation = nil
assert_nothing_raised do
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
end
- assert_equal :a, relation.klass
+ assert_equal FakeKlass, relation.klass
assert_equal :b, relation.table
assert !relation.loaded, 'relation is not loaded'
end
def test_responds_to_model_and_returns_klass
- relation = Relation.new :a, :b
- assert_equal :a, relation.model
+ relation = Relation.new FakeKlass, :b
+ assert_equal FakeKlass, relation.model
end
def test_initialize_single_values
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
@@ -33,19 +33,19 @@ module ActiveRecord
end
def test_multi_value_initialize
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
Relation::MULTI_VALUE_METHODS.each do |method|
assert_equal [], relation.send("#{method}_values"), method.to_s
end
end
def test_extensions
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
assert_equal [], relation.extensions
end
def test_empty_where_values_hash
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
assert_equal({}, relation.where_values_hash)
relation.where! :hello
@@ -79,7 +79,7 @@ module ActiveRecord
end
def test_scope_for_create
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
assert_equal({}, relation.scope_for_create)
end
@@ -110,31 +110,31 @@ module ActiveRecord
end
def test_empty_eager_loading?
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
assert !relation.eager_loading?
end
def test_eager_load_values
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
relation.eager_load! :b
assert relation.eager_loading?
end
def test_references_values
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
assert_equal [], relation.references_values
relation = relation.references(:foo).references(:omg, :lol)
assert_equal ['foo', 'omg', 'lol'], relation.references_values
end
def test_references_values_dont_duplicate
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
relation = relation.references(:foo).references(:foo)
assert_equal ['foo'], relation.references_values
end
test 'merging a hash into a relation' do
- relation = Relation.new :a, :b
+ relation = Relation.new FakeKlass, :b
relation = relation.merge where: :lol, readonly: true
assert_equal [:lol], relation.where_values
@@ -142,7 +142,7 @@ module ActiveRecord
end
test 'merging an empty hash into a relation' do
- assert_equal [], Relation.new(:a, :b).merge({}).where_values
+ assert_equal [], Relation.new(FakeKlass, :b).merge({}).where_values
end
test 'merging a hash with unknown keys raises' do
@@ -150,7 +150,7 @@ module ActiveRecord
end
test '#values returns a dup of the values' do
- relation = Relation.new(:a, :b).where! :foo
+ relation = Relation.new(FakeKlass, :b).where! :foo
values = relation.values
values[:where] = nil
@@ -158,18 +158,18 @@ module ActiveRecord
end
test 'relations can be created with a values hash' do
- relation = Relation.new(:a, :b, where: [:foo])
+ relation = Relation.new(FakeKlass, :b, where: [:foo])
assert_equal [:foo], relation.where_values
end
test 'merging a single where value' do
- relation = Relation.new(:a, :b)
+ relation = Relation.new(FakeKlass, :b)
relation.merge!(where: :foo)
assert_equal [:foo], relation.where_values
end
test 'merging a hash interpolates conditions' do
- klass = stub
+ klass = stub_everything
klass.stubs(:sanitize_sql).with(['foo = ?', 'bar']).returns('foo = bar')
relation = Relation.new(klass, :b)
@@ -179,8 +179,11 @@ module ActiveRecord
end
class RelationMutationTest < ActiveSupport::TestCase
+ class FakeKlass < Struct.new(:table_name, :name)
+ end
+
def relation
- @relation ||= Relation.new :a, :b
+ @relation ||= Relation.new FakeKlass, :b
end
(Relation::MULTI_VALUE_METHODS - [:references, :extending]).each do |method|
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index bc6cac0c6c..3a499a2025 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -157,19 +157,19 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 4, topics.to_a.size
assert_equal topics(:first).title, topics.first.title
end
-
+
def test_finding_with_assoc_order
topics = Topic.order(:id => :desc)
assert_equal 4, topics.to_a.size
assert_equal topics(:fourth).title, topics.first.title
end
-
+
def test_finding_with_reverted_assoc_order
topics = Topic.order(:id => :asc).reverse_order
assert_equal 4, topics.to_a.size
assert_equal topics(:fourth).title, topics.first.title
end
-
+
def test_raising_exception_on_invalid_hash_params
assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) }
end
@@ -509,7 +509,7 @@ class RelationTest < ActiveRecord::TestCase
def test_find_in_empty_array
authors = Author.all.where(:id => [])
- assert_blank authors.to_a
+ assert authors.to_a.blank?
end
def test_where_with_ar_object
@@ -723,7 +723,7 @@ class RelationTest < ActiveRecord::TestCase
def test_relation_merging_with_locks
devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2))
- assert_present devs.locked
+ assert devs.locked.present?
end
def test_relation_merging_with_preload
@@ -1438,4 +1438,47 @@ class RelationTest < ActiveRecord::TestCase
end
assert_no_queries { relation.to_a }
end
+
+ test 'group with select and includes' do
+ authors_count = Post.select('author_id, COUNT(author_id) AS num_posts').
+ group('author_id').order('author_id').includes(:author).to_a
+
+ assert_no_queries do
+ result = authors_count.map do |post|
+ [post.num_posts, post.author.try(:name)]
+ end
+
+ expected = [[1, nil], [5, "David"], [3, "Mary"], [2, "Bob"]]
+ assert_equal expected, result
+ end
+ end
+
+ test "delegations do not leak to other classes" do
+ Topic.all.by_lifo
+ assert Topic.all.class.method_defined?(:by_lifo)
+ assert !Post.all.respond_to?(:by_lifo)
+ end
+
+ class OMGTopic < ActiveRecord::Base
+ self.table_name = 'topics'
+
+ def self.__omg__
+ "omgtopic"
+ end
+ end
+
+ test "delegations do not clash across classes" do
+ begin
+ class ::Array
+ def __omg__
+ "array"
+ end
+ end
+
+ assert_equal "array", Topic.all.__omg__
+ assert_equal "omgtopic", OMGTopic.all.__omg__
+ ensure
+ Array.send(:remove_method, :__omg__)
+ end
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 7ff0044bd4..cae12e0e3a 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -112,7 +112,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*}, output
assert_no_match %r{c_int_4.*limit:}, output
- elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
assert_match %r{c_int_1.*limit: 1}, output
assert_match %r{c_int_2.*limit: 2}, output
assert_match %r{c_int_3.*limit: 3}, output
@@ -197,7 +197,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r(primary_key: "movieid"), match[1], "non-standard primary key not preserved"
end
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
def test_schema_dump_should_not_add_default_value_for_mysql_text_field
output = standard_dump
assert_match %r{t.text\s+"body",\s+null: false$}, output
@@ -280,6 +280,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dump_includes_ltrees_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_ltrees"} =~ output
+ assert_match %r[t.ltree "path"], output
+ end
+ end
+
def test_schema_dump_includes_arrays_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_arrays"} =~ output
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 068f3cf3cd..295c7e13fa 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,5 +1,6 @@
-require "cases/helper"
+require 'cases/helper'
require 'models/topic'
+require 'models/person'
require 'bcrypt'
class SerializedAttributeTest < ActiveRecord::TestCase
@@ -212,4 +213,25 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_kind_of BCrypt::Password, topic.content
assert_equal(true, topic.content == password, 'password should equal')
end
+
+ def test_serialize_attribute_via_select_method_when_time_zone_available
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ Topic.serialize(:content, MyObject)
+
+ myobj = MyObject.new('value1', 'value2')
+ topic = Topic.create(content: myobj)
+
+ assert_equal(myobj, Topic.select(:content).find(topic.id).content)
+ assert_raise(ActiveModel::MissingAttributeError) { Topic.select(:id).find(topic.id).content }
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ end
+
+ def test_serialize_attribute_can_be_serialized_in_an_integer_column
+ insures = ['life']
+ person = SerializedPerson.new(first_name: 'David', insures: insures)
+ assert person.save
+ person = person.reload
+ assert_equal(insures, person.insures)
+ end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 961ba8d9ba..869892e33f 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -244,90 +244,17 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal :rollback, @first.last_after_transaction_error
assert_equal [:after_rollback], @second.history
end
-end
-
-
-class TransactionObserverCallbacksTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
- fixtures :topics
-
- class TopicWithObserverAttached < ActiveRecord::Base
- self.table_name = :topics
- def history
- @history ||= []
- end
- end
-
- class TopicWithObserverAttachedObserver < ActiveRecord::Observer
- def after_commit(record)
- record.history.push "after_commit"
- end
- def after_rollback(record)
- record.history.push "after_rollback"
- end
+ def test_after_rollback_callbacks_should_validate_on_condition
+ assert_raise(ArgumentError) { Topic.send(:after_rollback, :on => :save) }
end
- def test_after_commit_called
- assert TopicWithObserverAttachedObserver.instance, 'should have observer'
-
- topic = TopicWithObserverAttached.new
- topic.save!
-
- assert_equal %w{ after_commit }, topic.history
- end
-
- def test_after_rollback_called
- assert TopicWithObserverAttachedObserver.instance, 'should have observer'
-
- topic = TopicWithObserverAttached.new
-
- Topic.transaction do
- topic.save!
- raise ActiveRecord::Rollback
- end
-
- assert topic.id.nil?
- assert !topic.persisted?
- assert_equal %w{ after_rollback }, topic.history
- end
-
- class TopicWithManualRollbackObserverAttached < ActiveRecord::Base
- self.table_name = :topics
- def history
- @history ||= []
- end
- end
-
- class TopicWithManualRollbackObserverAttachedObserver < ActiveRecord::Observer
- def after_save(record)
- record.history.push "after_save"
- raise ActiveRecord::Rollback
- end
- end
-
- def test_after_save_called_with_manual_rollback
- assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer'
-
- topic = TopicWithManualRollbackObserverAttached.new
-
- assert !topic.save
- assert_equal nil, topic.id
- assert !topic.persisted?
- assert_equal %w{ after_save }, topic.history
- end
- def test_after_save_called_with_manual_rollback_bang
- assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer'
-
- topic = TopicWithManualRollbackObserverAttached.new
-
- topic.save!
- assert_equal nil, topic.id
- assert !topic.persisted?
- assert_equal %w{ after_save }, topic.history
+ def test_after_commit_callbacks_should_validate_on_condition
+ assert_raise(ArgumentError) { Topic.send(:after_commit, :on => :save) }
end
end
+
class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb
index a396da6645..4f1cb99b68 100644
--- a/activerecord/test/cases/transaction_isolation_test.rb
+++ b/activerecord/test/cases/transaction_isolation_test.rb
@@ -77,7 +77,7 @@ class TransactionIsolationTest < ActiveRecord::TestCase
Tag.transaction(isolation: :repeatable_read) do
tag.reload
- Tag2.find(tag.id).update_attributes(name: 'emily')
+ Tag2.find(tag.id).update(name: 'emily')
tag.reload
assert_equal 'jon', tag.name
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index fdca10f4fb..bcbc48b38a 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -15,7 +15,7 @@ class TransactionTest < ActiveRecord::TestCase
end
def test_raise_after_destroy
- refute @first.frozen?
+ assert_not @first.frozen?
assert_raises(RuntimeError) {
Topic.transaction do
@@ -26,7 +26,7 @@ class TransactionTest < ActiveRecord::TestCase
}
assert @first.reload
- refute @first.frozen?
+ assert_not @first.frozen?
end
def test_successful
@@ -117,21 +117,21 @@ class TransactionTest < ActiveRecord::TestCase
assert !Topic.find(1).approved?
end
- def test_update_attributes_should_rollback_on_failure
+ def test_update_should_rollback_on_failure
author = Author.find(1)
posts_count = author.posts.size
assert posts_count > 0
- status = author.update_attributes(:name => nil, :post_ids => [])
+ status = author.update(name: nil, post_ids: [])
assert !status
assert_equal posts_count, author.posts(true).size
end
- def test_update_attributes_should_rollback_on_failure!
+ def test_update_should_rollback_on_failure!
author = Author.find(1)
posts_count = author.posts.size
assert posts_count > 0
assert_raise(ActiveRecord::RecordInvalid) do
- author.update_attributes!(:name => nil, :post_ids => [])
+ author.update!(name: nil, post_ids: [])
end
assert_equal posts_count, author.posts(true).size
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 46212e49b6..46e767af1a 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -30,6 +30,11 @@ class ReplyWithTitleObject < Reply
def title; ReplyTitle.new; end
end
+class Employee < ActiveRecord::Base
+ self.table_name = 'postgresql_arrays'
+ validates_uniqueness_of :nicknames
+end
+
class UniquenessValidationTest < ActiveRecord::TestCase
fixtures :topics, 'warehouse-things', :developers
@@ -341,16 +346,28 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert w6.errors[:city].any?, "Should have errors for city"
assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city"
end
-
+
def test_validate_uniqueness_with_conditions
Topic.validates_uniqueness_of(:title, :conditions => Topic.where('approved = ?', true))
Topic.create("title" => "I'm a topic", "approved" => true)
Topic.create("title" => "I'm an unapproved topic", "approved" => false)
-
+
t3 = Topic.new("title" => "I'm a topic", "approved" => true)
assert !t3.valid?, "t3 shouldn't be valid"
-
+
t4 = Topic.new("title" => "I'm an unapproved topic", "approved" => false)
assert t4.valid?, "t4 should be valid"
end
+
+ def test_validate_uniqueness_with_array_column
+ return skip "Uniqueness on arrays has only been tested in PostgreSQL so far." if !current_adapter? :PostgreSQLAdapter
+
+ e1 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [1000, 1200])
+ assert e1.persisted?, "Saving e1"
+
+ e2 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [2200])
+ assert !e2.persisted?, "e2 shouldn't be valid"
+ assert e2.errors[:nicknames].any?, "Should have errors for nicknames"
+ assert_equal ["has already been taken"], e2.errors[:nicknames], "Should have uniqueness message for nicknames"
+ end
end
diff --git a/activerecord/test/migrations/10_urban/9_add_expressions.rb b/activerecord/test/migrations/10_urban/9_add_expressions.rb
new file mode 100644
index 0000000000..79a342e574
--- /dev/null
+++ b/activerecord/test/migrations/10_urban/9_add_expressions.rb
@@ -0,0 +1,11 @@
+class AddExpressions < ActiveRecord::Migration
+ def self.up
+ create_table("expressions") do |t|
+ t.column :expression, :string
+ end
+ end
+
+ def self.down
+ drop_table "expressions"
+ end
+end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 77f4a2ec87..6935cfb0ea 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -1,5 +1,6 @@
class Author < ActiveRecord::Base
has_many :posts
+ has_one :post
has_many :very_special_comments, :through => :posts
has_many :posts_with_comments, -> { includes(:comments) }, :class_name => "Post"
has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, :class_name => "Post"
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index e4c0278c0d..0109ef4f83 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,6 +1,6 @@
class Bulb < ActiveRecord::Base
default_scope { where(:name => 'defaulty') }
- belongs_to :car
+ belongs_to :car, :touch => true
attr_reader :scope_after_initialize, :attributes_after_initialize
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 17b17724e8..3ca8f69646 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -111,6 +111,7 @@ end
class DependentFirm < Company
has_one :account, :foreign_key => "firm_id", :dependent => :nullify
has_many :companies, :foreign_key => 'client_of', :dependent => :nullify
+ has_one :company, :foreign_key => 'client_of', :dependent => :nullify
end
class RestrictedFirm < Company
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 622dd75aeb..683cb54a10 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -57,6 +57,16 @@ class Developer < ActiveRecord::Base
def log=(message)
audit_logs.build :message => message
end
+
+ after_find :track_instance_count
+ cattr_accessor :instance_count
+
+ def track_instance_count
+ self.class.instance_count ||= 0
+ self.class.instance_count += 1
+ end
+ private :track_instance_count
+
end
class AuditLog < ActiveRecord::Base
@@ -224,3 +234,8 @@ class ThreadsafeDeveloper < ActiveRecord::Base
limit(1)
end
end
+
+class CachedDeveloper < ActiveRecord::Base
+ self.table_name = "developers"
+ self.cache_timestamp_format = :number
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 6ad0cf6987..c602ca5eac 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -100,3 +100,24 @@ class NestedPerson < ActiveRecord::Base
assign_attributes({ :best_friend_attributes => { :first_name => new_name } })
end
end
+
+class Insure
+ INSURES = %W{life annuality}
+
+ def self.load mask
+ INSURES.select do |insure|
+ (1 << INSURES.index(insure)) & mask.to_i > 0
+ end
+ end
+
+ def self.dump insures
+ numbers = insures.map { |insure| INSURES.index(insure) }
+ numbers.inject(0) { |sum, n| sum + (1 << n) }
+ end
+end
+
+class SerializedPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ serialize :insures, Insure
+end
diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb
index 561b431766..c2f9068f57 100644
--- a/activerecord/test/models/reference.rb
+++ b/activerecord/test/models/reference.rb
@@ -4,15 +4,14 @@ class Reference < ActiveRecord::Base
has_many :agents_posts_authors, :through => :person
- class << self
- attr_accessor :make_comments
- end
+ class << self; attr_accessor :make_comments; end
+ self.make_comments = false
before_destroy :make_comments
def make_comments
if self.class.make_comments
- person.update_attributes :comments => "Reference destroyed"
+ person.update comments: "Reference destroyed"
end
end
end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index 079e325aad..c88262580e 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -1,8 +1,6 @@
require 'models/topic'
class Reply < Topic
- scope :base, -> { scoped }
-
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count"
has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id"
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index d0e7338f15..ae13f2cd8a 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,7 +1,7 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids
- postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name|
+ %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees
+ postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_intrange_data_type).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -89,6 +89,15 @@ _SQL
_SQL
end
+ if 't' == select_value("select 'ltree'=ANY(select typname from pg_type)")
+ execute <<_SQL
+ CREATE TABLE postgresql_ltrees (
+ id SERIAL PRIMARY KEY,
+ path ltree
+ );
+_SQL
+ end
+
if 't' == select_value("select 'json'=ANY(select typname from pg_type)")
execute <<_SQL
CREATE TABLE postgresql_json_data_type (
@@ -97,6 +106,16 @@ _SQL
);
_SQL
end
+
+ if 't' == select_value("select 'int4range'=ANY(select typname from pg_type)")
+ execute <<_SQL
+ CREATE TABLE postgresql_intrange_data_type (
+ id SERIAL PRIMARY KEY,
+ int_range int4range,
+ int_long_range int8range
+ );
+_SQL
+ end
execute <<_SQL
CREATE TABLE postgresql_moneys (
@@ -152,7 +171,7 @@ _SQL
);
_SQL
-begin
+ begin
execute <<_SQL
CREATE TABLE postgresql_partitioned_table_parent (
id SERIAL PRIMARY KEY,
@@ -174,14 +193,14 @@ begin
BEFORE INSERT ON postgresql_partitioned_table_parent
FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger();
_SQL
-rescue ActiveRecord::StatementInvalid => e
- if e.message =~ /language "plpgsql" does not exist/
- execute "CREATE LANGUAGE 'plpgsql';"
- retry
- else
- raise e
+ rescue ActiveRecord::StatementInvalid => e
+ if e.message =~ /language "plpgsql" does not exist/
+ execute "CREATE LANGUAGE 'plpgsql';"
+ retry
+ else
+ raise e
+ end
end
-end
begin
execute <<_SQL
@@ -190,7 +209,13 @@ end
data xml
);
_SQL
-rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
+ rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table
+ end
+
+ # This table is to verify if the :limit option is being ignored for text and binary columns
+ create_table :limitless_fields, force: true do |t|
+ t.binary :binary, limit: 100_000
+ t.text :text, limit: 100_000
end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 35778d008a..46219c53db 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -115,6 +115,7 @@ ActiveRecord::Schema.define do
t.integer :engines_count
t.integer :wheels_count
t.column :lock_version, :integer, :null => false, :default => 0
+ t.timestamps
end
create_table :categories, :force => true do |t|
@@ -493,6 +494,7 @@ ActiveRecord::Schema.define do
t.integer :followers_count, :default => 0
t.references :best_friend
t.references :best_friend_of
+ t.integer :insures, null: false, default: 0
t.timestamps
end
diff --git a/activerecord/test/support/mysql.rb b/activerecord/test/support/mysql.rb
deleted file mode 100644
index 7a66415e64..0000000000
--- a/activerecord/test/support/mysql.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-if defined?(Mysql)
- class Mysql
- class Error
- # This monkey patch fixes annoy warning with mysql-2.8.1.gem when executing testcases.
- def errno_with_fix_warnings
- silence_warnings { errno_without_fix_warnings }
- end
- alias_method_chain :errno, :fix_warnings
- end
- end
-end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index b55a706b2f..08bec2f4ae 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,94 @@
## Rails 4.0.0 (unreleased) ##
+* Deprecate `assert_present` and `assert_blank` in favor of
+ `assert object.blank?` and `assert object.present?`
+
+ *Yves Senn*
+
+* Change `String#to_date` to use `Date.parse`. This gives more consistent error
+ messages and allows the use of partial dates.
+
+ "gibberish".to_date => Argument Error: invalid date
+ "3rd Feb".to_date => Sun, 03 Feb 2013
+
+ *Kelly Stannard*
+
+* It's now possible to compare `Date`, `DateTime`, `Time` and `TimeWithZone`
+ with `Infinity`. This allows to create date/time ranges with one infinite bound.
+ Example:
+
+ range = Range.new(Date.today, Float::INFINITY)
+
+ Also it's possible to check inclusion of date/time in range with conversion.
+
+ range.include?(Time.now + 1.year) # => true
+ range.include?(DateTime.now + 1.year) # => true
+
+ *Alexander Grebennik*
+
+* Remove meaningless `ActiveSupport::FrozenObjectError`, which was just an alias of `RuntimeError`.
+
+ *Akira Matsuda*
+
+* Introduce `assert_not` to replace warty `assert !foo`. *Jeremy Kemper*
+
+* Prevent `Callbacks#set_callback` from setting the same callback twice.
+
+ before_save :foo, :bar, :foo
+
+ will at first call `bar`, then `foo`. `foo` will no more be called
+ twice.
+
+ *Dmitriy Kiriyenko*
+
+* Add `ActiveSupport::Logger#silence` that works the same as the old `Logger#silence` extension.
+
+ *DHH*
+
+* Remove surrogate unicode character encoding from `ActiveSupport::JSON.encode`
+ The encoding scheme was broken for unicode characters outside the basic multilingual plane;
+ since json is assumed to be `UTF-8`, and we already force the encoding to `UTF-8`,
+ simply pass through the un-encoded characters.
+
+ *Brett Carter*
+
+* Deprecate `Time.time_with_date_fallback`, `Time.utc_time` and `Time.local_time`.
+ These methods were added to handle the limited range of Ruby's native Time
+ implementation. Those limitations no longer apply so we are deprecating them in 4.0
+ and they will be removed in 4.1.
+
+ *Andrew White*
+
+* Deprecate `Date#to_time_in_current_zone` and add `Date#in_time_zone`. *Andrew White*
+
+* Add `String#in_time_zone` method to convert a string to an ActiveSupport::TimeWithZone. *Andrew White*
+
+* Deprecate `ActiveSupport::BasicObject` in favor of `ActiveSupport::ProxyObject`.
+ This class is used for proxy classes. It avoids confusion with Ruby's BasicObject
+ class.
+
+ *Francesco Rodriguez*
+
+* Patched Marshal#load to work with constant autoloading.
+ Fixes autoloading with cache stores that relay on Marshal(MemCacheStore and FileStore). [fixes #8167]
+
+ *Uriel Katz*
+
+* Make `Time.zone.parse` to work with JavaScript format date strings. *Andrew White*
+
+* Add `DateTime#seconds_until_end_of_day` and `Time#seconds_until_end_of_day`
+ as a complement for `seconds_from_midnight`; useful when setting expiration
+ times for caches, e.g.:
+
+ <% cache('dashboard', expires_in: Date.current.seconds_until_end_of_day) do %>
+ ...
+
+ *Olek Janiszewski*
+
+* No longer proxy ActiveSupport::Multibyte#class. *Steve Klabnik*
+
+* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. *Carlos Antonio da Silva*
+
* `XmlMini.with_backend` now may be safely used with threads:
Thread.new do
@@ -43,7 +132,7 @@
* Hash#extract! returns only those keys that present in the receiver.
- {:a => 1, :b => 2}.extract!(:a, :x) # => {:a => 1}
+ {a: 1, b: 2}.extract!(:a, :x) # => {:a => 1}
*Mikhail Dieterle*
@@ -61,7 +150,7 @@
*Jeremy Kemper*
-* Add logger.push_tags and .pop_tags to complement logger.tagged:
+* Add `logger.push_tags` and `.pop_tags` to complement logger.tagged:
class Job
def before
@@ -121,7 +210,7 @@
You can choose which instance of the deprecator will be used.
- deprecate :method_name, :deprecator => deprecator_instance
+ deprecate :method_name, deprecator: deprecator_instance
You can use ActiveSupport::Deprecation in your gem.
@@ -139,7 +228,7 @@
def new_method
end
- deprecate :old_method => :new_method, :deprecator => deprecator
+ deprecate old_method: :new_method, deprecator: deprecator
end
MyGem.new.old_method
@@ -270,8 +359,6 @@
* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva*
-* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva*
-
* Deprecates the compatibility method Module#local_constant_names,
use Module#local_constants instead (which returns symbols). *fxn*
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index c2bcf1d3e7..6aeeb7132d 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2012 David Heinemeier Hansson
+Copyright (c) 2005-2013 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index a4216d2cb4..4c9e59dbd2 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -24,4 +24,5 @@ Gem::Specification.new do |s|
s.add_dependency 'multi_json', '~> 1.3'
s.add_dependency 'tzinfo', '~> 0.3.33'
s.add_dependency 'minitest', '~> 4.1'
+ s.add_dependency 'thread_safe','~> 0.1'
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 4e397ea110..ffa6ffda4f 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2012 David Heinemeier Hansson
+# Copyright (c) 2005-2013 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -40,6 +40,7 @@ module ActiveSupport
eager_autoload do
autoload :BacktraceCleaner
autoload :BasicObject
+ autoload :ProxyObject
autoload :Benchmarkable
autoload :Cache
autoload :Callbacks
diff --git a/activesupport/lib/active_support/basic_object.rb b/activesupport/lib/active_support/basic_object.rb
index 6ccb0cd525..91aac6db64 100644
--- a/activesupport/lib/active_support/basic_object.rb
+++ b/activesupport/lib/active_support/basic_object.rb
@@ -1,13 +1,11 @@
-module ActiveSupport
- # A class with no predefined methods that behaves similarly to Builder's
- # BlankSlate. Used for proxy classes.
- class BasicObject < ::BasicObject
- undef_method :==
- undef_method :equal?
+require 'active_support/deprecation'
+require 'active_support/proxy_object'
- # Let ActiveSupport::BasicObject at least raise exceptions.
- def raise(*args)
- ::Object.send(:raise, *args)
+module ActiveSupport
+ class BasicObject < ProxyObject # :nodoc:
+ def self.inherited(*)
+ ::ActiveSupport::Deprecation.warn 'ActiveSupport::BasicObject is deprecated! Use ActiveSupport::ProxyObject instead.'
+ super
end
end
end
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 0595446189..1cd0c2f790 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -2,6 +2,20 @@ require 'active_support/deprecation'
require 'active_support/logger'
module ActiveSupport
- BufferedLogger = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
- 'BufferedLogger', '::ActiveSupport::Logger')
+ class BufferedLogger < Logger
+
+ def initialize(*args)
+ self.class._deprecation_warning
+ super
+ end
+
+ def self.inherited(*)
+ _deprecation_warning
+ super
+ end
+
+ def self._deprecation_warning
+ ::ActiveSupport::Deprecation.warn 'ActiveSupport::BufferedLogger is deprecated! Use ActiveSupport::Logger instead.'
+ end
+ end
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 9a53870b3d..5a5548d567 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -3,7 +3,6 @@ require 'zlib'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/benchmark'
-require 'active_support/core_ext/exception'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
@@ -582,7 +581,7 @@ module ActiveSupport
end
# Returns the size of the cached value. This could be less than
- # <tt>value.size</tt> if the data is compressed.
+ # <tt>value.size</tt> if the data is compressed.
def size
if defined?(@s)
@s
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 2c1ad60d44..8e265ad863 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/marshal'
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
require 'uri/common'
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 17450fe4d0..712db2c75a 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -6,6 +6,7 @@ rescue LoadError => e
end
require 'digest/md5'
+require 'active_support/core_ext/marshal'
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 8199f431f1..e3e1845868 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,3 +1,4 @@
+require 'thread_safe'
require 'active_support/concern'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/class/attribute'
@@ -14,7 +15,7 @@ module ActiveSupport
# Mixing in this module allows you to define the events in the object's
# lifecycle that will support callbacks (via +ClassMethods.define_callbacks+),
# set the instance methods, procs, or callback objects to be called (via
- # +ClassMethods.set_callback+), and run the installed callbacks at the
+ # +ClassMethods.set_callback+), and run the installed callbacks at the
# appropriate times (via +run_callbacks+).
#
# Three kinds of callbacks are supported: before callbacks, run before a
@@ -133,6 +134,10 @@ module ActiveSupport
@kind == _kind && @filter == _filter
end
+ def duplicates?(other)
+ matches?(other.kind, other.filter)
+ end
+
def _update_filter(filter_options, new_options)
filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
@@ -327,6 +332,30 @@ module ActiveSupport
method.join("\n")
end
+ def append(*callbacks)
+ callbacks.each { |c| append_one(c) }
+ end
+
+ def prepend(*callbacks)
+ callbacks.each { |c| prepend_one(c) }
+ end
+
+ private
+
+ def append_one(callback)
+ remove_duplicates(callback)
+ push(callback)
+ end
+
+ def prepend_one(callback)
+ remove_duplicates(callback)
+ unshift(callback)
+ end
+
+ def remove_duplicates(callback)
+ delete_if { |c| callback.duplicates?(c) }
+ end
+
end
module ClassMethods
@@ -351,10 +380,18 @@ module ActiveSupport
undef_method(name) if method_defined?(name)
end
- def __callback_runner_name(kind)
+ def __callback_runner_name_cache
+ @__callback_runner_name_cache ||= ThreadSafe::Cache.new {|cache, kind| cache[kind] = __generate_callback_runner_name(kind) }
+ end
+
+ def __generate_callback_runner_name(kind)
"_run__#{self.name.hash.abs}__#{kind}__callbacks"
end
+ def __callback_runner_name(kind)
+ __callback_runner_name_cache[kind]
+ end
+
# This is used internally to append, prepend and skip callbacks to the
# CallbackChain.
def __update_callbacks(name, filters = [], block = nil) #:nodoc:
@@ -382,7 +419,7 @@ module ActiveSupport
# set_callback :save, :before_meth
#
# The callback can specified as a symbol naming an instance method; as a
- # proc, lambda, or block; as a string to be instance evaluated; or as an
+ # proc, lambda, or block; as a string to be instance evaluated; or as an
# object that responds to a certain method determined by the <tt>:scope</tt>
# argument to +define_callback+.
#
@@ -412,11 +449,7 @@ module ActiveSupport
Callback.new(chain, filter, type, options.dup, self)
end
- filters.each do |filter|
- chain.delete_if {|c| c.matches?(type, filter) }
- end
-
- options[:prepend] ? chain.unshift(*(mapped.reverse)) : chain.push(*mapped)
+ options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
target.send("_#{name}_callbacks=", chain)
end
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 16d2a6a290..e0d39d509f 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -38,7 +38,7 @@ module ActiveSupport
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.
+ # through config. Also look at the example for config to contrast.
#
# Defines both class and instance config accessors.
#
@@ -75,7 +75,7 @@ module ActiveSupport
# end
#
# User.allowed_access = false
- #  User.allowed_access # => false
+ # User.allowed_access # => false
#
# User.new.allowed_access = true # => NoMethodError
# User.new.allowed_access # => NoMethodError
@@ -88,7 +88,7 @@ module ActiveSupport
# end
#
# User.allowed_access = false
- #  User.allowed_access # => false
+ # User.allowed_access # => false
#
# User.new.allowed_access = true # => NoMethodError
# User.new.allowed_access # => NoMethodError
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index ff06436bd6..64e9945ef5 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -26,7 +26,7 @@ class Array
#
# [].to_sentence # => ""
# ['one'].to_sentence # => "one"
- # ['one', 'two'].to_sentence # => "one and two"
+ # ['one', 'two'].to_sentence # => "one and two"
# ['one', 'two', 'three'].to_sentence # => "one, two, and three"
#
# ['one', 'two'].to_sentence(passing: 'invalid option')
@@ -41,7 +41,7 @@ class Array
# Examples using <tt>:locale</tt> option:
#
# # Given this locale dictionary:
- # # 
+ # #
# # es:
# # support:
# # array:
@@ -53,7 +53,7 @@ class Array
# # => "uno y dos"
#
# ['uno', 'dos', 'tres'].to_sentence(locale: :es)
- # # => "uno o dos o al menos tres"
+ # # => "uno o dos o al menos tres"
def to_sentence(options = {})
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index 05b09a4c7f..1245768870 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -29,8 +29,7 @@ class Array
#
# [*object]
#
- # which for +nil+ returns <tt>[nil]</tt> (Ruby 1.8.7) or <tt>[]</tt> (Ruby
- # 1.9), and calls to <tt>Array(object)</tt> otherwise.
+ # which for +nil+ returns <tt>[]</tt>, and calls to <tt>Array(object)</tt> otherwise.
#
# Thus, in this case the behavior may be different for +nil+, and the differences with
# <tt>Kernel#Array</tt> explained above apply to the rest of <tt>object</tt>s.
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 1c3d26ead4..5d8d09aa69 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -69,9 +69,13 @@ class Class
# To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
def class_attribute(*attrs)
options = attrs.extract_options!
- instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
+ # double assignment is used to avoid "assigned but unused variable" warning
+ instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
+ # We use class_eval here rather than define_method because class_attribute
+ # may be used in a performance sensitive context therefore the overhead that
+ # define_method introduces may become significant.
attrs.each do |name|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def self.#{name}() nil end
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index c2e0ebb3d4..9a2dc6e7c5 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -31,7 +31,7 @@ class Class
# class Bar < Foo; end
# class Baz < Foo; end
#
- # Foo.subclasses # => [Baz, Bar]
+ # Foo.subclasses # => [Baz, Bar]
def subclasses
subclasses, chain = [], descendants
chain.each do |k|
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 465fedda80..5f13f5f70f 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -2,4 +2,5 @@ require 'active_support/core_ext/date/acts_like'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
+require 'active_support/core_ext/date/infinite_comparable'
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 439d380af7..421aa12100 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -53,19 +53,19 @@ class Date
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then subtracts the specified number of seconds.
def ago(seconds)
- to_time_in_current_zone.since(-seconds)
+ in_time_zone.since(-seconds)
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
# and then adds the specified number of seconds
def since(seconds)
- to_time_in_current_zone.since(seconds)
+ in_time_zone.since(seconds)
end
alias :in :since
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
def beginning_of_day
- to_time_in_current_zone
+ in_time_zone
end
alias :midnight :beginning_of_day
alias :at_midnight :beginning_of_day
@@ -73,8 +73,9 @@ class Date
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
def end_of_day
- to_time_in_current_zone.end_of_day
+ in_time_zone.end_of_day
end
+ alias :at_end_of_day :end_of_day
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 9120b0ba49..fe08ade7e0 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -75,10 +75,10 @@ class Date
#
# date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
def to_time(form = :local)
- ::Time.send("#{form}_time", year, month, day)
+ ::Time.send(form, year, month, day)
end
def xmlschema
- to_time_in_current_zone.xmlschema
+ in_time_zone.xmlschema
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb
new file mode 100644
index 0000000000..ca5d793942
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/infinite_comparable'
+
+class Date
+ include InfiniteComparable
+end
diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb
index c1b3934722..b4548671bf 100644
--- a/activesupport/lib/active_support/core_ext/date/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date/zones.rb
@@ -2,14 +2,36 @@ require 'date'
require 'active_support/core_ext/time/zones'
class Date
+ # *DEPRECATED*: Use +Date#in_time_zone+ instead.
+ #
# Converts Date to a TimeWithZone in the current zone if <tt>Time.zone</tt> or
# <tt>Time.zone_default</tt> is set, otherwise converts Date to a Time via
# Date#to_time.
def to_time_in_current_zone
+ ActiveSupport::Deprecation.warn 'Date#to_time_in_current_zone is deprecated. Use Date#in_time_zone instead', caller
+
if ::Time.zone
::Time.zone.local(year, month, day)
else
to_time
end
end
+
+ # Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default
+ # is set, otherwise converts Date to a Time via Date#to_time
+ #
+ # Time.zone = 'Hawaii' # => 'Hawaii'
+ # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
+ #
+ # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument,
+ # and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
+ #
+ # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
+ def in_time_zone(zone = ::Time.zone)
+ if zone
+ ::Time.find_zone!(zone).local(year, month, day)
+ else
+ to_time
+ end
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index e8a27b9f38..024af91738 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -2,3 +2,4 @@ require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date_time/zones'
+require 'active_support/core_ext/date_time/infinite_comparable'
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 0c6437b02b..4e4852a5e6 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -32,6 +32,15 @@ class DateTime
sec + (min * 60) + (hour * 3600)
end
+ # Returns the number of seconds until 23:59:59.
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
+ # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
+ # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
+ def seconds_until_end_of_day
+ end_of_day.to_i - to_i
+ end
+
# Returns a new DateTime where one or more of the elements have been changed
# according to the +options+ parameter. The time options (<tt>:hour</tt>,
# <tt>:minute</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
@@ -101,6 +110,7 @@ class DateTime
def end_of_day
change(:hour => 23, :min => 59, :sec => 59)
end
+ alias :at_end_of_day :end_of_day
# Returns a new DateTime representing the start of the hour (hh:00:00).
def beginning_of_hour
@@ -112,6 +122,7 @@ class DateTime
def end_of_hour
change(:min => 59, :sec => 59)
end
+ alias :at_end_of_hour :end_of_hour
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
#
@@ -131,11 +142,4 @@ class DateTime
def utc_offset
(offset * 86400).to_i
end
-
- # Layers additional behavior on DateTime#<=> so that Time and
- # ActiveSupport::TimeWithZone instances can be compared with a DateTime.
- def <=>(other)
- super other.to_datetime
- end
-
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb
new file mode 100644
index 0000000000..8a282b19f2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/infinite_comparable'
+
+class DateTime
+ include InfiniteComparable
+end
diff --git a/activesupport/lib/active_support/core_ext/exception.rb b/activesupport/lib/active_support/core_ext/exception.rb
deleted file mode 100644
index ba7757ea07..0000000000
--- a/activesupport/lib/active_support/core_ext/exception.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module ActiveSupport
- FrozenObjectError = RuntimeError
-end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 85b0e10be2..6cb7434e5f 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -88,56 +88,55 @@ class Hash
end
class << self
+ # Returns a Hash containing a collection of pairs when the key is the node name and the value is
+ # its content
+ #
+ # xml = <<-XML
+ # <?xml version="1.0" encoding="UTF-8"?>
+ # <hash>
+ # <foo type="integer">1</foo>
+ # <bar type="integer">2</bar>
+ # </hash>
+ # XML
+ #
+ # hash = Hash.from_xml(xml)
+ # # => {"hash"=>{"foo"=>1, "bar"=>2}}
def from_xml(xml)
- typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)))
+ ActiveSupport::XMLConverter.new(xml).to_h
+ end
+
+ end
+end
+
+module ActiveSupport
+ class XMLConverter # :nodoc:
+ def initialize(xml)
+ @xml = normalize_keys(XmlMini.parse(xml))
+ end
+
+ def to_h
+ deep_to_h(@xml)
end
private
- def typecast_xml_value(value)
- case value
+
+ def normalize_keys(params)
+ case params
when Hash
- if value['type'] == 'array'
- _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
- if entries.nil? || (c = value['__content__'] && c.blank?)
- []
- else
- case entries # something weird with classes not matching here. maybe singleton methods breaking is_a?
- when Array
- entries.collect { |v| typecast_xml_value(v) }
- when Hash
- [typecast_xml_value(entries)]
- else
- raise "can't typecast #{entries.inspect}"
- end
- end
- elsif value['type'] == 'file' ||
- (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
- content = value['__content__']
- if parser = ActiveSupport::XmlMini::PARSING[value['type']]
- parser.arity == 1 ? parser.call(content) : parser.call(content, value)
- else
- content
- end
- elsif value['type'] == 'string' && value['nil'] != 'true'
- ''
- # blank or nil parsed values are represented by nil
- elsif value.blank? || value['nil'] == 'true'
- nil
- # If the type is the only element which makes it then
- # this still makes the value nil, except if type is
- # a XML node(where type['value'] is a Hash)
- elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
- nil
- else
- xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }]
+ Hash[params.map { |k,v| [k.to_s.tr('-', '_'), normalize_keys(v)] } ]
+ when Array
+ params.map { |v| normalize_keys(v) }
+ else
+ params
+ end
+ end
- # 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
+ def deep_to_h(value)
+ case value
+ when Hash
+ process_hash(value)
when Array
- value.map! { |i| typecast_xml_value(i) }
- value.length > 1 ? value : value.first
+ process_array(value)
when String
value
else
@@ -145,15 +144,79 @@ class Hash
end
end
- def unrename_keys(params)
- case params
- when Hash
- Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ]
- when Array
- params.map { |v| unrename_keys(v) }
+ def process_hash(value)
+ if become_array?(value)
+ _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
+ if entries.nil? || value['__content__'].try(:empty?)
+ []
else
- params
+ case entries
+ when Array
+ entries.collect { |v| deep_to_h(v) }
+ when Hash
+ [deep_to_h(entries)]
+ else
+ raise "can't typecast #{entries.inspect}"
+ end
+ end
+ elsif become_content?(value)
+ process_content(value)
+
+ elsif become_empty_string?(value)
+ ''
+ elsif become_hash?(value)
+ xml_value = Hash[value.map { |k,v| [k, deep_to_h(v)] }]
+
+ # 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
end
+
+ def become_content?(value)
+ value['type'] == 'file' || (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
+ end
+
+ def become_array?(value)
+ value['type'] == 'array'
+ end
+
+ def become_empty_string?(value)
+ # {"string" => true}
+ # No tests fail when the second term is removed.
+ value['type'] == 'string' && value['nil'] != 'true'
+ end
+
+ def become_hash?(value)
+ !nothing?(value) && !garbage?(value)
+ end
+
+ def nothing?(value)
+ # blank or nil parsed values are represented by nil
+ value.blank? || value['nil'] == 'true'
+ end
+
+ def garbage?(value)
+ # If the type is the only element which makes it then
+ # this still makes the value nil, except if type is
+ # a XML node(where type['value'] is a Hash)
+ value['type'] && !value['type'].is_a?(::Hash) && value.size == 1
+ end
+
+ def process_content(value)
+ content = value['__content__']
+ if parser = ActiveSupport::XmlMini::PARSING[value['type']]
+ parser.arity == 1 ? parser.call(content) : parser.call(content, value)
+ else
+ content
+ end
+ end
+
+ def process_array(value)
+ value.map! { |i| deep_to_h(i) }
+ value.length > 1 ? value : value.first
+ end
+
end
end
+
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 5cb00d0ebd..d90e996ad4 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -2,7 +2,7 @@ class Hash
# Return a hash that includes everything but the given keys. This is useful for
# limiting a set of parameters to everything but a few known toggles:
#
- # @person.update_attributes(params[:person].except(:admin))
+ # @person.update(params[:person].except(:admin))
def except(*keys)
dup.except!(*keys)
end
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 6c7e876fca..981e8436bf 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -16,7 +16,7 @@ class Hash
# converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
# desirable.
#
- # b = { b: 1 }
+ # b = { b: 1 }
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
alias nested_under_indifferent_access with_indifferent_access
end
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 13081995b0..b4c451ace4 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -24,7 +24,7 @@ class Hash
# Return a new hash with all keys converted to strings.
#
- # hash = { name: 'Rob', age: '28' }
+ # hash = { name: 'Rob', age: '28' }
#
# hash.stringify_keys
# #=> { "name" => "Rob", "age" => "28" }
@@ -44,7 +44,7 @@ class Hash
# hash = { 'name' => 'Rob', 'age' => '28' }
#
# hash.symbolize_keys
- # #=> { name: "Rob", age: "28" }
+ # #=> { name: "Rob", age: "28" }
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
@@ -102,7 +102,7 @@ class Hash
# This includes the keys from the root hash and from all
# nested hashes.
#
- # hash = { person: { name: 'Rob', age: '28' } }
+ # hash = { person: { name: 'Rob', age: '28' } }
#
# hash.deep_stringify_keys
# # => { "person" => { "name" => "Rob", "age" => "28" } }
@@ -121,10 +121,10 @@ class Hash
# 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 = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
#
# hash.deep_symbolize_keys
- # # => { person: { name: "Rob", age: "28" } }
+ # # => { person: { name: "Rob", age: "28" } }
def deep_symbolize_keys
deep_transform_keys{ |key| key.to_sym rescue key }
end
diff --git a/activesupport/lib/active_support/core_ext/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/infinite_comparable.rb
new file mode 100644
index 0000000000..b78b2deaad
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/infinite_comparable.rb
@@ -0,0 +1,35 @@
+require 'active_support/concern'
+require 'active_support/core_ext/module/aliasing'
+require 'active_support/core_ext/object/try'
+
+module InfiniteComparable
+ extend ActiveSupport::Concern
+
+ included do
+ alias_method_chain :<=>, :infinity
+ end
+
+ define_method :'<=>_with_infinity' do |other|
+ if other.class == self.class
+ public_send :'<=>_without_infinity', other
+ else
+ infinite = try(:infinite?)
+ other_infinite = other.try(:infinite?)
+
+ # inf <=> inf
+ if infinite && other_infinite
+ infinite <=> other_infinite
+ # not_inf <=> inf
+ elsif other_infinite
+ -other_infinite
+ # inf <=> not_inf
+ elsif infinite
+ infinite
+ else
+ conversion = "to_#{self.class.name.downcase}"
+ other = other.public_send(conversion) if other.respond_to?(conversion)
+ public_send :'<=>_without_infinity', other
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index 9fb4f6b73a..82080ffe51 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -1,3 +1,6 @@
+require 'active_support/duration'
+require 'active_support/core_ext/numeric/time'
+
class Integer
# Enables the use of time calculations and declarations, like <tt>45.minutes +
# 2.hours + 4.years</tt>.
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 7b518821c8..79d3303b41 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -59,10 +59,9 @@ module Kernel
#
# puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
def suppress(*exception_classes)
- begin yield
- rescue Exception => e
- raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
- end
+ yield
+ rescue Exception => e
+ raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
end
# Captures the given stream and returns it:
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index 16fce81445..34de766331 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/deprecation'
+require 'active_support/logger_silence'
ActiveSupport::Deprecation.warn 'this file is deprecated and will be removed'
@@ -31,27 +32,9 @@ require 'logger'
#
# logger.datetime_format = "%Y-%m-%d"
#
-# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger
+# Note: This logger is deprecated in favor of ActiveSupport::Logger
class Logger
- ##
- # :singleton-method:
- # Set to false to disable the silencer
- cattr_accessor :silencer
- self.silencer = true
-
- # Silences the logger for the duration of the block.
- def silence(temporary_level = Logger::ERROR)
- if silencer
- begin
- old_logger_level, self.level = level, temporary_level
- yield self
- ensure
- self.level = old_logger_level
- end
- else
- yield self
- end
- end
+ include LoggerSilence
alias :old_datetime_format= :datetime_format=
# Logging date-time format (string passed to +strftime+). Ignored if the formatter
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
new file mode 100644
index 0000000000..c7a8348b1d
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -0,0 +1,19 @@
+module Marshal
+ class << self
+ def load_with_autoloading(source)
+ load_without_autoloading(source)
+ rescue ArgumentError, NameError => exc
+ if exc.message.match(%r|undefined class/module (.+)|)
+ # try loading the class/module
+ $1.constantize
+ # if it is a IO we need to go back to read the object
+ source.rewind if source.respond_to?(:rewind)
+ retry
+ else
+ raise exc
+ end
+ end
+
+ alias_method_chain :load, :autoloading
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index a6bc0624be..d5cfc2ece4 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/numeric/conversions'
+require 'active_support/core_ext/numeric/infinite_comparable'
diff --git a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb
new file mode 100644
index 0000000000..b5f1b0487b
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb
@@ -0,0 +1,9 @@
+require 'active_support/core_ext/infinite_comparable'
+
+class Float
+ include InfiniteComparable
+end
+
+class BigDecimal
+ include InfiniteComparable
+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 f55fbc282e..1d639f3af6 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -32,7 +32,7 @@ end
class Hash
# Returns a deep copy of hash.
#
- # hash = { a: { b: 'b' } }
+ # hash = { a: { b: 'b' } }
# dup = hash.deep_dup
# dup[:a][:c] = 'c'
#
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index ad864765a3..5d7cb81e38 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -11,3 +11,4 @@ require 'active_support/core_ext/string/exclude'
require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/inquiry'
require 'active_support/core_ext/string/indent'
+require 'active_support/core_ext/string/zones'
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 9b9d83932e..c795df124b 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -21,7 +21,7 @@ class String
date_values[6] *= 1000000
offset = date_values.pop
- ::Time.send("#{form}_time", *date_values) - offset
+ ::Time.send(form, *date_values) - offset
end
end
@@ -32,11 +32,7 @@ class String
# "2012-12-13".to_date #=> Thu, 13 Dec 2012
# "12/13/2012".to_date #=> ArgumentError: invalid date
def to_date
- unless blank?
- date_values = ::Date._parse(self, false).values_at(:year, :mon, :mday)
-
- ::Date.new(*date_values)
- end
+ ::Date.parse(self, false) unless blank?
end
# Converts a string to a DateTime value.
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 341e2deec9..6522145572 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -193,8 +193,8 @@ class String
# Capitalizes the first word, turns underscores into spaces, and strips '_id'.
# Like +titleize+, this is meant for creating pretty output.
#
- # 'employee_salary' # => "Employee salary"
- # 'author_id' # => "Author"
+ # 'employee_salary'.humanize # => "Employee salary"
+ # 'author_id'.humanize # => "Author"
def humanize
ActiveSupport::Inflector.humanize(self)
end
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 4e7824ad74..a124202936 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -6,7 +6,7 @@ class String
#
# +mb_chars+ is a multibyte safe proxy for string methods.
#
- # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
+ # It creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
# encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
#
@@ -17,9 +17,6 @@ class String
# name.mb_chars.reverse.to_s # => "rellüM sualC"
# name.mb_chars.length # => 12
#
- # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
- # it becomes easy to run one version of your code on multiple Ruby versions.
- #
# == Method chaining
#
# All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
@@ -36,11 +33,7 @@ class String
# For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
# information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
def mb_chars
- if ActiveSupport::Multibyte.proxy_class.consumes?(self)
- ActiveSupport::Multibyte.proxy_class.new(self)
- else
- self
- end
+ ActiveSupport::Multibyte.proxy_class.new(self)
end
def is_utf8?
diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb
new file mode 100644
index 0000000000..e3f20eee29
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/string/zones.rb
@@ -0,0 +1,13 @@
+require 'active_support/core_ext/time/zones'
+
+class String
+ # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default
+ # is set, otherwise converts String to a Time via String#to_time
+ def in_time_zone(zone = ::Time.zone)
+ if zone
+ ::Time.find_zone!(zone).parse(self)
+ else
+ to_time
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb
new file mode 100644
index 0000000000..5481766f10
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/thread.rb
@@ -0,0 +1,74 @@
+class Thread
+ LOCK = Mutex.new # :nodoc:
+
+ # Returns the value of a thread local variable that has been set. Note that
+ # these are different than fiber local values.
+ #
+ # Thread local values are carried along with threads, and do not respect
+ # fibers. For example:
+ #
+ # Thread.new {
+ # Thread.current.thread_variable_set("foo", "bar") # set a thread local
+ # Thread.current["foo"] = "bar" # set a fiber local
+ #
+ # Fiber.new {
+ # Fiber.yield [
+ # Thread.current.thread_variable_get("foo"), # get the thread local
+ # Thread.current["foo"], # get the fiber local
+ # ]
+ # }.resume
+ # }.join.value # => ['bar', nil]
+ #
+ # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned
+ # for the fiber local. The fiber is executed in the same thread, so the
+ # thread local values are available.
+ def thread_variable_get(key)
+ locals[key.to_sym]
+ end
+
+ # Sets a thread local with +key+ to +value+. Note that these are local to
+ # threads, and not to fibers. Please see Thread#thread_variable_get for
+ # more information.
+ def thread_variable_set(key, value)
+ locals[key.to_sym] = value
+ end
+
+ # Returns an an array of the names of the thread-local variables (as Symbols).
+ #
+ # thr = Thread.new do
+ # Thread.current.thread_variable_set(:cat, 'meow')
+ # Thread.current.thread_variable_set("dog", 'woof')
+ # end
+ # thr.join #=> #<Thread:0x401b3f10 dead>
+ # thr.thread_variables #=> [:dog, :cat]
+ #
+ # Note that these are not fiber local variables. Please see Thread#thread_variable_get
+ # for more details.
+ def thread_variables
+ locals.keys
+ end
+
+ # Returns <tt>true</tt> if the given string (or symbol) exists as a
+ # thread-local variable.
+ #
+ # me = Thread.current
+ # me.thread_variable_set(:oliver, "a")
+ # me.thread_variable?(:oliver) #=> true
+ # me.thread_variable?(:stanley) #=> false
+ #
+ # Note that these are not fiber local variables. Please see Thread#thread_variable_get
+ # for more details.
+ def thread_variable?(key)
+ locals.has_key?(key.to_sym)
+ end
+
+ private
+
+ def locals
+ if defined?(@locals)
+ @locals
+ else
+ LOCK.synchronize { @locals ||= {} }
+ end
+ end
+end unless Thread.instance_methods.include?(:thread_variable_set)
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 32cffe237d..af6b589b71 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -3,3 +3,4 @@ require 'active_support/core_ext/time/calculations'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/marshal'
require 'active_support/core_ext/time/zones'
+require 'active_support/core_ext/time/infinite_comparable'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 931851d40e..1f95f62229 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/time/conversions'
require 'active_support/time_with_zone'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date_and_time/calculations'
+require 'active_support/deprecation'
class Time
include DateAndTime::Calculations
@@ -25,10 +26,13 @@ class Time
end
end
+ # *DEPRECATED*: Use +Time#utc+ or +Time#local+ instead.
+ #
# Returns a new Time if requested year can be accommodated by Ruby's Time class
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
# otherwise returns a DateTime.
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
+ ActiveSupport::Deprecation.warn 'time_with_datetime_fallback is deprecated. Use Time#utc or Time#local instead', caller
time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
# This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138.
@@ -41,13 +45,19 @@ class Time
::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
end
+ # *DEPRECATED*: Use +Time#utc+ instead.
+ #
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:utc</tt>.
def utc_time(*args)
+ ActiveSupport::Deprecation.warn 'utc_time is deprecated. Use Time#utc instead', caller
time_with_datetime_fallback(:utc, *args)
end
+ # *DEPRECATED*: Use +Time#local+ instead.
+ #
# Wraps class method +time_with_datetime_fallback+ with +utc_or_local+ set to <tt>:local</tt>.
def local_time(*args)
+ ActiveSupport::Deprecation.warn 'local_time is deprecated. Use Time#local instead', caller
time_with_datetime_fallback(:local, *args)
end
@@ -62,6 +72,15 @@ class Time
to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
end
+ # Returns the number of seconds until 23:59:59.
+ #
+ # Time.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399
+ # Time.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103
+ # Time.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0
+ def seconds_until_end_of_day
+ end_of_day.to_i - to_i
+ end
+
# Returns a new Time where one or more of the elements have been changed according
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>) reset cascadingly, so if only the hour is passed,
@@ -151,6 +170,7 @@ class Time
:usec => Rational(999999999, 1000)
)
end
+ alias :at_end_of_day :end_of_day
# Returns a new Time representing the start of the hour (x:00)
def beginning_of_hour
@@ -166,6 +186,7 @@ class Time
:usec => Rational(999999999, 1000)
)
end
+ alias :at_end_of_hour :end_of_hour
# Returns a Range representing the whole day of the current time.
def all_day
diff --git a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb
new file mode 100644
index 0000000000..63795885f5
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/infinite_comparable'
+
+class Time
+ include InfiniteComparable
+end
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 1bf622d6a6..497c4c3fb8 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -24,7 +24,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
def _dump(*args)
obj = dup
obj.instance_variable_set('@_zone', zone)
- obj._dump_without_zone(*args)
+ obj.send :_dump_without_zone, *args
end
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index c75fb46263..fff4c776a9 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,5 +1,6 @@
require 'set'
require 'thread'
+require 'thread_safe'
require 'pathname'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
@@ -44,7 +45,7 @@ module ActiveSupport #:nodoc:
self.autoload_once_paths = []
# An array of qualified constant names that have been loaded. Adding a name
- # to this array will cause it to be unloaded the next time Dependencies are
+ # to this array will cause it to be unloaded the next time Dependencies are
# cleared.
mattr_accessor :autoloaded_constants
self.autoloaded_constants = []
@@ -344,7 +345,7 @@ module ActiveSupport #:nodoc:
# Given +path+, a filesystem path to a ruby file, return an array of
# constant paths which would cause Dependencies to attempt to load this
- # file.
+ # file.
def loadable_constants_for_path(path, bases = autoload_paths)
path = $` if path =~ /\.rb\z/
expanded_path = File.expand_path(path)
@@ -394,7 +395,7 @@ module ActiveSupport #:nodoc:
# Attempt to autoload the provided module name by searching for a directory
# matching the expected path suffix. If found, the module is created and
# assigned to +into+'s constants with the name +const_name+. Provided that
- # the directory was loaded from a reloadable base path, it is added to the
+ # the directory was loaded from a reloadable base path, it is added to the
# set of constants that are to be unloaded.
def autoload_module!(into, const_name, qualified_name, path_suffix)
return nil unless base_path = autoloadable_module?(path_suffix)
@@ -517,7 +518,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = Hash.new
+ @store = ThreadSafe::Cache.new
end
def empty?
@@ -644,46 +645,58 @@ module ActiveSupport #:nodoc:
normalized = const.to_s.sub(/\A::/, '')
normalized.sub!(/\A(Object::)+/, '')
- constants = normalized.split('::')
- to_remove = constants.pop
- parent_name = constants.empty? ? 'Object' : constants.join('::')
+ constants = normalized.split('::')
+ to_remove = constants.pop
- if parent = safe_constantize(parent_name)
- log "removing constant #{const}"
-
- # In an autoloaded user.rb like this
- #
- # autoload :Foo, 'foo'
- #
- # class User < ActiveRecord::Base
- # end
- #
- # we correctly register "Foo" as being autoloaded. But if the app does
- # not use the "Foo" constant we need to be careful not to trigger
- # loading "foo.rb" ourselves. While #const_defined? and #const_get? do
- # require the file, #autoload? and #remove_const don't.
+ if constants.empty?
+ parent = Object
+ else
+ # This method is robust to non-reachable constants.
#
- # We are going to remove the constant nonetheless ---which exists as
- # far as Ruby is concerned--- because if the user removes the macro
- # call from a class or module that were not autoloaded, as in the
- # example above with Object, accessing to that constant must err.
- unless parent.autoload?(to_remove)
- begin
- constantized = parent.const_get(to_remove, false)
- rescue NameError
- log "the constant #{const} is not reachable anymore, skipping"
- return
- else
- constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
- end
- end
+ # Non-reachable constants may be passed if some of the parents were
+ # autoloaded and already removed. It is easier to do a sanity check
+ # here than require the caller to be clever. We check the parent
+ # rather than the very const argument because we do not want to
+ # trigger Kernel#autoloads, see the comment below.
+ parent_name = constants.join('::')
+ return unless qualified_const_defined?(parent_name)
+ parent = constantize(parent_name)
+ end
+ log "removing constant #{const}"
+
+ # In an autoloaded user.rb like this
+ #
+ # autoload :Foo, 'foo'
+ #
+ # class User < ActiveRecord::Base
+ # end
+ #
+ # we correctly register "Foo" as being autoloaded. But if the app does
+ # not use the "Foo" constant we need to be careful not to trigger
+ # loading "foo.rb" ourselves. While #const_defined? and #const_get? do
+ # require the file, #autoload? and #remove_const don't.
+ #
+ # We are going to remove the constant nonetheless ---which exists as
+ # far as Ruby is concerned--- because if the user removes the macro
+ # call from a class or module that were not autoloaded, as in the
+ # example above with Object, accessing to that constant must err.
+ unless parent.autoload?(to_remove)
begin
- parent.instance_eval { remove_const to_remove }
+ constantized = parent.const_get(to_remove, false)
rescue NameError
log "the constant #{const} is not reachable anymore, skipping"
+ return
+ else
+ constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
end
end
+
+ begin
+ parent.instance_eval { remove_const to_remove }
+ rescue NameError
+ log "the constant #{const} is not reachable anymore, skipping"
+ end
end
protected
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index 9fc58a338f..c0dba5f7fd 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -34,7 +34,7 @@ module ActiveSupport
def autoload(const_name, path = @_at_path)
unless path
- full = [name, @_under_path, const_name.to_s, path].compact.join("::")
+ full = [name, @_under_path, const_name.to_s].compact.join("::")
path = Inflector.underscore(full)
end
diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb
index ff240cb887..8472a58add 100644
--- a/activesupport/lib/active_support/deprecation/instance_delegator.rb
+++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb
@@ -3,13 +3,13 @@ require 'active_support/core_ext/module/delegation'
module ActiveSupport
class Deprecation
- module InstanceDelegator
+ module InstanceDelegator # :nodoc:
def self.included(base)
base.extend(ClassMethods)
base.public_class_method :new
end
- module ClassMethods
+ module ClassMethods # :nodoc:
def include(included_module)
included_module.instance_methods.each { |m| method_added(m) }
super
@@ -21,4 +21,4 @@ module ActiveSupport
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 17e69c34a5..485dc91063 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -30,7 +30,7 @@ module ActiveSupport
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
# @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
#
- # When someone execute any method expect +inspect+ on proxy object this will
+ # When someone executes any method except +inspect+ on proxy object this will
# trigger +warn+ method on +deprecator_instance+.
#
# Default deprecator is <tt>ActiveSupport::Deprecation</tt>
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 7e99646117..2cb1f408b6 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,4 +1,4 @@
-require 'active_support/basic_object'
+require 'active_support/proxy_object'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/object/acts_like'
@@ -7,7 +7,7 @@ module ActiveSupport
# Time#advance, respectively. It mainly supports the methods on Numeric.
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
- class Duration < BasicObject
+ class Duration < ProxyObject
attr_accessor :value, :parts
def initialize(value, parts) #:nodoc:
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index a6b9aa3503..20136dd1b0 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -68,7 +68,7 @@ module ActiveSupport
end
# Executes the given block and updates the latest watched files and
- # timestamp.
+ # timestamp.
def execute
@last_watched = watched
@last_update_at = updated_at(@last_watched)
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index af506d6f2e..9cf4b2b2ba 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,3 +1,4 @@
+require 'thread_safe'
require 'active_support/core_ext/array/prepend_and_append'
require 'active_support/i18n'
@@ -24,9 +25,10 @@ module ActiveSupport
# singularization rules that is runs. This guarantees that your rules run
# before any of the rules that may already have been loaded.
class Inflections
+ @__instance__ = ThreadSafe::Cache.new
+
def self.instance(locale = :en)
- @__instance__ ||= Hash.new { |h, k| h[k] = new }
- @__instance__[locale]
+ @__instance__[locale] ||= new
end
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
@@ -36,7 +38,7 @@ module ActiveSupport
end
# Private, for the test suite.
- def initialize_dup(orig) # :nodoc:
+ def initialize_dup(orig) # :nodoc:
%w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
instance_variable_set("@#{scope}", orig.send(scope).dup)
end
@@ -44,7 +46,7 @@ module ActiveSupport
# Specifies a new acronym. An acronym must be specified as it will appear
# in a camelized string. An underscore string that contains the acronym
- # will retain the acronym when passed to +camelize+, +humanize+, or
+ # will retain the acronym when passed to +camelize+, +humanize+, or
# +titleize+. A camelized string that contains the acronym will maintain
# the acronym when titleized or humanized, and will convert the acronym
# into a non-delimited single lowercase word when passed to +underscore+.
@@ -79,7 +81,7 @@ module ActiveSupport
#
# +acronym+ may be used to specify any word that contains an acronym or
# otherwise needs to maintain a non-standard capitalization. The only
- # restriction is that the word must begin with a capital letter.
+ # restriction is that the word must begin with a capital letter.
#
# acronym 'RESTful'
# underscore 'RESTful' #=> 'restful'
@@ -97,7 +99,7 @@ module ActiveSupport
end
# Specifies a new pluralization rule and its replacement. The rule can
- # either be a string or a regular expression. The replacement should
+ # either be a string or a regular expression. The replacement should
# always be a string that may include references to the matched data from
# the rule.
def plural(rule, replacement)
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 1eb2b4212b..39648727fd 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -266,14 +266,12 @@ module ActiveSupport
# 'UnknownModule'.safe_constantize # => nil
# 'UnknownModule::Foo::Bar'.safe_constantize # => nil
def safe_constantize(camel_cased_word)
- begin
- constantize(camel_cased_word)
- rescue NameError => e
- raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
- e.name.to_s == camel_cased_word.to_s
- rescue ArgumentError => e
- raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
- end
+ constantize(camel_cased_word)
+ rescue NameError => e
+ raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
+ e.name.to_s == camel_cased_word.to_s
+ rescue ArgumentError => e
+ raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
end
# Returns the suffix that should be added to a number to denote the position
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 7a5c351ca8..832d1ce6d5 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -129,13 +129,7 @@ module ActiveSupport
def escape(string)
string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
- json = string.
- gsub(escape_regex) { |s| ESCAPED_CHARS[s] }.
- gsub(/([\xC0-\xDF][\x80-\xBF]|
- [\xE0-\xEF][\x80-\xBF]{2}|
- [\xF0-\xF7][\x80-\xBF]{3})+/nx) { |s|
- s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
- }
+ json = string.gsub(escape_regex) { |s| ESCAPED_CHARS[s] }
json = %("#{json}")
json.force_encoding(::Encoding::UTF_8)
json
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 6beb2b6afa..71654dbb87 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -1,4 +1,4 @@
-require 'mutex_m'
+require 'thread_safe'
require 'openssl'
module ActiveSupport
@@ -28,16 +28,14 @@ module ActiveSupport
class CachingKeyGenerator
def initialize(key_generator)
@key_generator = key_generator
- @cache_keys = {}.extend(Mutex_m)
+ @cache_keys = ThreadSafe::Cache.new
end
# Returns a derived key suitable for use. The default key_size is chosen
# to be compatible with the default settings of ActiveSupport::MessageVerifier.
# i.e. OpenSSL::Digest::SHA1#block_length
def generate_key(salt, key_size=64)
- @cache_keys.synchronize do
- @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
- end
+ @cache_keys["#{salt}#{key_size}"] ||= @key_generator.generate_key(salt, key_size)
end
end
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 65202f99fc..4a55bbb350 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,7 +1,11 @@
+require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/logger_silence'
require 'logger'
module ActiveSupport
class Logger < ::Logger
+ include LoggerSilence
+
# Broadcasts logs to multiple loggers.
def self.broadcast(logger) # :nodoc:
Module.new do
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
new file mode 100644
index 0000000000..a8efdef944
--- /dev/null
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -0,0 +1,24 @@
+require 'active_support/concern'
+
+module LoggerSilence
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_accessor :silencer
+ self.silencer = true
+ end
+
+ # Silences the logger for the duration of the block.
+ def silence(temporary_level = Logger::ERROR)
+ if silencer
+ begin
+ old_logger_level, self.level = level, temporary_level
+ yield self
+ ensure
+ self.level = old_logger_level
+ end
+ else
+ yield self
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 1588674afc..b7dc0689b0 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -33,7 +33,7 @@ module ActiveSupport
# the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
# bits. If you are using a user-entered secret, you can generate a suitable
# key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or
- # similar.
+ # similar.
#
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
@@ -50,7 +50,7 @@ module ActiveSupport
end
# Encrypt and sign a message. We need to sign the message in order to avoid
- # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
+ # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
def encrypt_and_sign(value)
verifier.generate(_encrypt(value))
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 140b6ca08d..a87383fe99 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -6,7 +6,7 @@ module ActiveSupport
# signed to prevent tampering.
#
# This is useful for cases like remember-me tokens and auto-unsubscribe links
- # where the session store isn't suitable or available.
+ # where the session store isn't suitable or available.
#
# Remember Me:
# cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb
index 1bf8e618ad..ffebd9a60b 100644
--- a/activesupport/lib/active_support/multibyte.rb
+++ b/activesupport/lib/active_support/multibyte.rb
@@ -5,7 +5,7 @@ module ActiveSupport #:nodoc:
# The proxy class returned when calling mb_chars. You can use this accessor
# to configure your own proxy class so you can support other encodings. See
- # the ActiveSupport::Multibyte::Chars implementation for an example how to
+ # the ActiveSupport::Multibyte::Chars implementation for an example how to
# do this.
#
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 2e5bcf4639..7588fdb67c 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,4 +1,5 @@
require 'mutex_m'
+require 'thread_safe'
module ActiveSupport
module Notifications
@@ -11,7 +12,7 @@ module ActiveSupport
def initialize
@subscribers = []
- @listeners_for = {}
+ @listeners_for = ThreadSafe::Cache.new
super
end
@@ -44,7 +45,9 @@ module ActiveSupport
end
def listeners_for(name)
- synchronize do
+ # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics)
+ @listeners_for[name] || synchronize do
+ # use synchronisation when accessing @subscribers
@listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
end
end
diff --git a/activesupport/lib/active_support/proxy_object.rb b/activesupport/lib/active_support/proxy_object.rb
new file mode 100644
index 0000000000..a2bdf1d790
--- /dev/null
+++ b/activesupport/lib/active_support/proxy_object.rb
@@ -0,0 +1,13 @@
+module ActiveSupport
+ # A class with no predefined methods that behaves similarly to Builder's
+ # BlankSlate. Used for proxy classes.
+ class ProxyObject < ::BasicObject
+ undef_method :==
+ undef_method :equal?
+
+ # Let ActiveSupport::BasicObject at least raise exceptions.
+ def raise(*args)
+ ::Object.send(:raise, *args)
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/queueing.rb b/activesupport/lib/active_support/queueing.rb
deleted file mode 100644
index a89a48d057..0000000000
--- a/activesupport/lib/active_support/queueing.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-require 'delegate'
-require 'thread'
-
-module ActiveSupport
- # A Queue that simply inherits from STDLIB's Queue. When this
- # queue is used, Rails automatically starts a job runner in a
- # background thread.
- class Queue < ::Queue
- attr_writer :consumer
-
- def initialize(consumer_options = {})
- super()
- @consumer_options = consumer_options
- end
-
- def consumer
- @consumer ||= ThreadedQueueConsumer.new(self, @consumer_options)
- end
-
- # Drain the queue, running all jobs in a different thread. This method
- # may not be available on production queues.
- def drain
- # run the jobs in a separate thread so assumptions of synchronous
- # jobs are caught in test mode.
- consumer.drain
- end
- end
-
- class SynchronousQueue < Queue
- def push(job)
- super.tap { drain }
- end
- alias << push
- alias enq push
- end
-
- # In test mode, the Rails queue is backed by an Array so that assertions
- # can be made about its contents. The test queue provides a +jobs+
- # method to make assertions about the queue's contents and a +drain+
- # method to drain the queue and run the jobs.
- #
- # Jobs are run in a separate thread to catch mistakes where code
- # assumes that the job is run in the same thread.
- class TestQueue < Queue
- # Get a list of the jobs off this queue. This method may not be
- # available on production queues.
- def jobs
- @que.dup
- end
-
- # Marshal and unmarshal job before pushing it onto the queue. This will
- # raise an exception on any attempts in tests to push jobs that can't (or
- # shouldn't) be marshalled.
- def push(job)
- super Marshal.load(Marshal.dump(job))
- end
- end
-
- # The threaded consumer will run jobs in a background thread in
- # development mode or in a VM where running jobs on a thread in
- # production mode makes sense.
- #
- # When the process exits, the consumer pushes a nil onto the
- # queue and joins the thread, which will ensure that all jobs
- # are executed before the process finally dies.
- class ThreadedQueueConsumer
- attr_accessor :logger
-
- def initialize(queue, options = {})
- @queue = queue
- @logger = options[:logger]
- @fallback_logger = Logger.new($stderr)
- end
-
- def start
- @thread = Thread.new { consume }
- self
- end
-
- def shutdown
- @queue.push nil
- @thread.join
- end
-
- def drain
- @queue.pop.run until @queue.empty?
- end
-
- def consume
- while job = @queue.pop
- run job
- end
- end
-
- def run(job)
- job.run
- rescue Exception => exception
- handle_exception job, exception
- end
-
- def handle_exception(job, exception)
- (logger || @fallback_logger).error "Job Error: #{job.inspect}\n#{exception.message}\n#{exception.backtrace.join("\n")}"
- end
- end
-end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 133aa6a054..72ac597d99 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -13,20 +13,6 @@ module ActiveSupport
end
end
- # Sets the default value for Time.zone
- # If assigned value cannot be matched to a TimeZone, an exception will be raised.
- initializer "active_support.initialize_time_zone" do |app|
- require 'active_support/core_ext/time/zones'
- zone_default = Time.find_zone!(app.config.time_zone)
-
- unless zone_default
- raise 'Value assigned to config.time_zone not recognized. ' \
- 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
- end
-
- Time.zone_default = zone_default
- end
-
# Sets the default week start
# If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
initializer "active_support.initialize_beginning_of_week" do |app|
@@ -42,5 +28,21 @@ module ActiveSupport
ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
end
end
+
+ # Sets the default value for Time.zone after initialization since the default configuration
+ # lives in application initializers.
+ # If assigned value cannot be matched to a TimeZone, an exception will be raised.
+ config.after_initialize do |app|
+ require 'active_support/core_ext/time/zones'
+ zone_default = Time.find_zone!(app.config.time_zone)
+
+ unless zone_default
+ raise 'Value assigned to config.time_zone not recognized. ' \
+ 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
+ end
+
+ Time.zone_default = zone_default
+ end
+
end
end
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index 33810442da..18bc919734 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -11,7 +11,7 @@ module ActiveSupport
# logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
#
# This is used by the default Rails.logger as configured by Railties to make
- # it easy to stamp log lines with subdomains, request ids, and anything else
+ # it easy to stamp log lines with subdomains, request ids, and anything else
# to aid debugging of multi-user production applications.
module TaggedLogging
module Formatter # :nodoc:
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index c96ad17aba..8b392c36d0 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,9 +1,11 @@
gem 'minitest' # make sure we get the gem, not stdlib
-require 'minitest/spec'
+require 'minitest/unit'
require 'active_support/testing/tagged_logging'
require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
+require 'active_support/testing/pending'
+require 'active_support/testing/declarative'
require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/core_ext/kernel/reporting'
@@ -15,13 +17,7 @@ rescue LoadError
end
module ActiveSupport
- class TestCase < ::MiniTest::Spec
-
- # Use AS::TestCase for the base class when describing a model
- register_spec_type(self) do |desc|
- Class === desc && desc < ActiveRecord::Base
- end
-
+ class TestCase < ::MiniTest::Unit::TestCase
Assertion = MiniTest::Assertion
alias_method :method_name, :__name__
@@ -40,34 +36,26 @@ module ActiveSupport
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
-
- def self.describe(text)
- if block_given?
- super
- else
- message = "`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n"
- ActiveSupport::Deprecation.warn message
-
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.name
- "#{text}"
- end
- RUBY_EVAL
- end
- end
-
- class << self
- alias :test :it
- end
+ include ActiveSupport::Testing::Pending
+ extend ActiveSupport::Testing::Declarative
# test/unit backwards compatibility methods
alias :assert_raise :assert_raises
- alias :assert_not_nil :refute_nil
+ alias :assert_not_empty :refute_empty
alias :assert_not_equal :refute_equal
+ alias :assert_not_in_delta :refute_in_delta
+ alias :assert_not_in_epsilon :refute_in_epsilon
+ alias :assert_not_includes :refute_includes
+ alias :assert_not_instance_of :refute_instance_of
+ alias :assert_not_kind_of :refute_kind_of
alias :assert_no_match :refute_match
+ alias :assert_not_nil :refute_nil
+ alias :assert_not_operator :refute_operator
+ alias :assert_not_predicate :refute_predicate
+ alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Fails if the block raises an exception.
+ # Fails if the block raises an exception.
#
# assert_nothing_raised do
# ...
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 8466049e20..175f7ffe5a 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -3,6 +3,22 @@ require 'active_support/core_ext/object/blank'
module ActiveSupport
module Testing
module Assertions
+ # Assert that an expression is not truthy. Passes if <tt>object</tt> is
+ # +nil+ or +false+. "Truthy" means "considered true in a conditional"
+ # like <tt>if foo</tt>.
+ #
+ # assert_not nil # => true
+ # assert_not false # => true
+ # assert_not 'foo' # => 'foo' is not nil or false
+ #
+ # An error message can be specified.
+ #
+ # assert_not foo, 'foo should be false'
+ def assert_not(object, message = nil)
+ message ||= "Expected #{mu_pp(object)} to be nil or false"
+ assert !object, message
+ end
+
# Test numeric difference between the return value of an expression as a
# result of what is evaluated in the yielded block.
#
@@ -87,6 +103,7 @@ module ActiveSupport
#
# assert_blank [], 'this should be blank'
def assert_blank(object, message=nil)
+ ActiveSupport::Deprecation.warn('"assert_blank" is deprecated. Please use "assert object.blank?" instead')
message ||= "#{object.inspect} is not blank"
assert object.blank?, message
end
@@ -101,6 +118,7 @@ module ActiveSupport
#
# assert_present({ data: 'x' }, 'this should not be blank')
def assert_present(object, message=nil)
+ ActiveSupport::Deprecation.warn('"assert_present" is deprecated. Please use "assert object.present?" instead')
message ||= "#{object.inspect} is blank"
assert object.present?, message
end
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
new file mode 100644
index 0000000000..c446adc16d
--- /dev/null
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -0,0 +1,5 @@
+gem 'minitest'
+
+require 'minitest/unit'
+
+MiniTest::Unit.autorun
diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb
index 73e87befb6..52bfeb7179 100644
--- a/activesupport/lib/active_support/testing/constant_lookup.rb
+++ b/activesupport/lib/active_support/testing/constant_lookup.rb
@@ -30,7 +30,7 @@ module ActiveSupport
module ConstantLookup
extend ::ActiveSupport::Concern
- module ClassMethods
+ module ClassMethods # :nodoc:
def determine_constant_from_test_name(test_name)
names = test_name.split "::"
while names.size > 0 do
diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb
new file mode 100644
index 0000000000..508e37254a
--- /dev/null
+++ b/activesupport/lib/active_support/testing/declarative.rb
@@ -0,0 +1,40 @@
+module ActiveSupport
+ module Testing
+ module Declarative
+
+ def self.extended(klass) #:nodoc:
+ klass.class_eval do
+
+ unless method_defined?(:describe)
+ def self.describe(text)
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def self.name
+ "#{text}"
+ end
+ RUBY_EVAL
+ end
+ end
+
+ end
+ end
+
+ unless defined?(Spec)
+ # test "verify something" do
+ # ...
+ # end
+ def test(name, &block)
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
+ defined = instance_method(test_name) rescue false
+ raise "#{test_name} is already defined in #{self}" if defined
+ if block_given?
+ define_method(test_name, &block)
+ else
+ define_method(test_name) do
+ flunk "No implementation provided for #{name}"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 27d444fd91..aa87598926 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -12,8 +12,8 @@ module ActiveSupport
end
class ProxyTestResult
- def initialize
- @calls = []
+ def initialize(calls = [])
+ @calls = calls
end
def add_error(e)
@@ -27,6 +27,14 @@ module ActiveSupport
end
end
+ def marshal_dump
+ @calls
+ end
+
+ def marshal_load(calls)
+ initialize(calls)
+ end
+
def method_missing(name, *args)
@calls << [name, args]
end
diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb
new file mode 100644
index 0000000000..b04bbbbaea
--- /dev/null
+++ b/activesupport/lib/active_support/testing/pending.rb
@@ -0,0 +1,14 @@
+require 'active_support/deprecation'
+
+module ActiveSupport
+ module Testing
+ module Pending # :nodoc:
+ unless defined?(Spec)
+ def pending(description = "", &block)
+ ActiveSupport::Deprecation.warn("#pending is deprecated and will be removed in Rails 4.1, please use #skip instead.")
+ skip(description.blank? ? nil : description)
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb
index 8ea2605733..9d43eb179f 100644
--- a/activesupport/lib/active_support/testing/tagged_logging.rb
+++ b/activesupport/lib/active_support/testing/tagged_logging.rb
@@ -1,26 +1,25 @@
module ActiveSupport
module Testing
- module TaggedLogging
+ # Logs a "PostsControllerTest: test name" heading before each test to
+ # make test.log easier to search and follow along with.
+ module TaggedLogging #:nodoc:
attr_writer :tagged_logger
def before_setup
- tagged_logger.push_tags(self.class.name, __name__) if tagged_logging?
- super
- end
-
- def after_teardown
+ if tagged_logger
+ heading = "#{self.class}: #{__name__}"
+ divider = '-' * heading.size
+ tagged_logger.info divider
+ tagged_logger.info heading
+ tagged_logger.info divider
+ end
super
- tagged_logger.pop_tags(2) if tagged_logging?
end
private
def tagged_logger
@tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
end
-
- def tagged_logging?
- tagged_logger && tagged_logger.respond_to?(:push_tags)
- end
end
end
end
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index bcd5d78b54..92a593965e 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -9,21 +9,12 @@ end
require 'date'
require 'time'
-require 'active_support/core_ext/time/marshal'
-require 'active_support/core_ext/time/acts_like'
-require 'active_support/core_ext/time/calculations'
-require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/time/zones'
-
-require 'active_support/core_ext/date/acts_like'
-require 'active_support/core_ext/date/calculations'
-require 'active_support/core_ext/date/conversions'
-require 'active_support/core_ext/date/zones'
-
-require 'active_support/core_ext/date_time/acts_like'
-require 'active_support/core_ext/date_time/calculations'
-require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/date_time/zones'
+require 'active_support/core_ext/time'
+require 'active_support/core_ext/date'
+require 'active_support/core_ext/date_time'
require 'active_support/core_ext/integer/time'
require 'active_support/core_ext/numeric/time'
+
+require 'active_support/core_ext/string/conversions'
+require 'active_support/core_ext/string/zones'
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index b931de3fac..fdaaacf2fe 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -80,16 +80,29 @@ module ActiveSupport
end
alias_method :getlocal, :localtime
+ # Returns true if the current time is within Daylight Savings Time for the
+ # specified time zone.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # Time.zone.parse("2012-5-30").dst? # => true
+ # Time.zone.parse("2012-11-30").dst? # => false
def dst?
period.dst?
end
alias_method :isdst, :dst?
+ # Returns true if the current time zone is set to UTC.
+ #
+ # Time.zone = 'UTC' # => 'UTC'
+ # Time.zone.now.utc? # => true
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # Time.zone.now.utc? # => false
def utc?
time_zone.name == 'UTC'
end
alias_method :gmt?, :utc?
+ # Returns the offset from current time to UTC time in seconds.
def utc_offset
period.utc_total_offset
end
@@ -147,10 +160,18 @@ module ActiveSupport
end
end
+ # Returns a string of the object's date and time in the format used by
+ # HTTP requests.
+ #
+ # Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT"
def httpdate
utc.httpdate
end
+ # Returns a string of the object's date and time in the RFC 2822 standard
+ # format.
+ #
+ # Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000"
def rfc2822
to_s(:rfc822)
end
@@ -344,7 +365,7 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0)
+ ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000))
end
def duration_of_variable_length?(obj)
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 0207f53238..c5fbddcb5f 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -252,7 +252,7 @@ module ActiveSupport
# Time.zone = 'Hawaii' # => "Hawaii"
# Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
def local(*args)
- time = Time.utc_time(*args)
+ time = Time.utc(*args)
ActiveSupport::TimeWithZone.new(nil, self, time)
end
@@ -278,18 +278,23 @@ module ActiveSupport
# Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
def parse(str, now=now)
- date_parts = Date._parse(str)
- return if date_parts.empty?
- time = Time.parse(str, now) rescue DateTime.parse(str)
-
- if date_parts[:offset].nil?
- if date_parts[:hour] && time.hour != date_parts[:hour]
- time = DateTime.parse(str)
- end
-
- ActiveSupport::TimeWithZone.new(nil, self, time)
+ parts = Date._parse(str, false)
+ return if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year, now.year),
+ parts.fetch(:mon, now.month),
+ parts.fetch(:mday, now.day),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+
+ if parts[:offset]
+ TimeWithZone.new(time.utc, self)
else
- time.in_time_zone(self)
+ TimeWithZone.new(nil, self, time)
end
end
@@ -321,7 +326,7 @@ module ActiveSupport
end
# Available so that TimeZone instances respond like TZInfo::Timezone
- # instances.
+ # instances.
def period_for_utc(time)
tzinfo.period_for_utc(time)
end
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 8a67b148c3..90e50f235b 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -15,7 +15,7 @@ silence_warnings do
Encoding.default_external = "UTF-8"
end
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'empty_bool'
ENV['NO_RELOAD'] = '1'
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index ed903746c8..5158bbc196 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -1,6 +1,7 @@
require 'logger'
require 'abstract_unit'
require 'active_support/cache'
+require 'dependecies_test_helpers'
class CacheKeyTest < ActiveSupport::TestCase
def test_entry_legacy_optional_ivars
@@ -562,6 +563,45 @@ module LocalCacheBehavior
end
end
+module AutoloadingCacheBehavior
+ include DependeciesTestHelpers
+ def test_simple_autoloading
+ with_autoloading_fixtures do
+ @cache.write('foo', E.new)
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of E, @cache.read('foo')
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+ end
+
+ def test_two_classes_autoloading
+ with_autoloading_fixtures do
+ @cache.write('foo', [E.new, ClassFolder.new])
+ end
+
+ remove_constants(:E, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ loaded = @cache.read('foo')
+ assert_kind_of Array, loaded
+ assert_equal 2, loaded.size
+ assert_kind_of E, loaded[0]
+ assert_kind_of ClassFolder, loaded[1]
+ end
+
+ remove_constants(:E, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+ end
+end
+
class FileStoreTest < ActiveSupport::TestCase
def setup
Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
@@ -585,6 +625,7 @@ class FileStoreTest < ActiveSupport::TestCase
include LocalCacheBehavior
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
+ include AutoloadingCacheBehavior
def test_clear
filepath = File.join(cache_dir, ".gitkeep")
@@ -634,7 +675,7 @@ class FileStoreTest < ActiveSupport::TestCase
def test_log_exception_when_cache_read_fails
File.expects(:exist?).raises(StandardError, "failed")
@cache.send(:read_entry, "winston", {})
- assert_present @buffer.string
+ assert @buffer.string.present?
end
end
@@ -745,6 +786,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase
include LocalCacheBehavior
include CacheIncrementDecrementBehavior
include EncodedKeyCacheBehavior
+ include AutoloadingCacheBehavior
def test_raw_values
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
@@ -855,12 +897,12 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
def test_logging
@cache.fetch('foo') { 'bar' }
- assert_present @buffer.string
+ assert @buffer.string.present?
end
def test_mute_logging
@cache.mute { @cache.fetch('foo') { 'bar' } }
- assert_blank @buffer.string
+ assert @buffer.string.blank?
end
end
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 8810302f40..13f2e3cdaf 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -297,7 +297,7 @@ module CallbacksTest
end
end
end
-
+
class AroundPersonResult < MySuper
attr_reader :result
@@ -308,7 +308,7 @@ module CallbacksTest
def tweedle_dum
@result = yield
end
-
+
def tweedle_1
:tweedle_1
end
@@ -316,7 +316,7 @@ module CallbacksTest
def tweedle_2
:tweedle_2
end
-
+
def save
run_callbacks :save do
:running
@@ -410,7 +410,7 @@ module CallbacksTest
], around.history
end
end
-
+
class AroundCallbackResultTest < ActiveSupport::TestCase
def test_save_around
around = AroundPersonResult.new
@@ -607,6 +607,45 @@ module CallbacksTest
end
end
+ class OneTwoThreeSave
+ include ActiveSupport::Callbacks
+
+ define_callbacks :save
+
+ attr_accessor :record
+
+ def initialize
+ @record = []
+ end
+
+ def save
+ run_callbacks :save do
+ @record << "yielded"
+ end
+ end
+
+ def first
+ @record << "one"
+ end
+
+ def second
+ @record << "two"
+ end
+
+ def third
+ @record << "three"
+ end
+ end
+
+ class DuplicatingCallbacks < OneTwoThreeSave
+ set_callback :save, :before, :first, :second
+ set_callback :save, :before, :first, :third
+ end
+
+ class DuplicatingCallbacksInSameCall < OneTwoThreeSave
+ set_callback :save, :before, :first, :second, :first, :third
+ end
+
class UsingObjectTest < ActiveSupport::TestCase
def test_before_object
u = UsingObjectBefore.new
@@ -709,5 +748,18 @@ module CallbacksTest
end
end
end
-
+
+ class ExcludingDuplicatesCallbackTest < ActiveSupport::TestCase
+ def test_excludes_duplicates_in_separate_calls
+ model = DuplicatingCallbacks.new
+ model.save
+ assert_equal ["two", "one", "three", "yielded"], model.record
+ end
+
+ def test_excludes_duplicates_in_one_call
+ model = DuplicatingCallbacksInSameCall.new
+ model.save
+ assert_equal ["two", "one", "three", "yielded"], model.record
+ end
+ end
end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index 215a6e06b0..d00273a028 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -38,7 +38,7 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
assert_equal :bar, Parent.config.foo
end
- test "configuration accessors is not available on instance" do
+ test "configuration accessors are not available on instance" do
instance = Parent.new
assert !instance.respond_to?(:bar)
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 7ae1f67785..ec47d0632c 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -34,7 +34,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_to_time
assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time
- assert_equal Time.local_time(2039, 2, 21), Date.new(2039, 2, 21).to_time
+ assert_equal Time.local(2039, 2, 21), Date.new(2039, 2, 21).to_time
silence_warnings do
0.upto(138) do |year|
[:utc, :local].each do |format|
@@ -353,4 +353,17 @@ class DateExtBehaviorTest < ActiveSupport::TestCase
Date.today.freeze.freeze
end
end
+
+ def test_compare_with_infinity
+ assert_equal(-1, Date.today <=> Float::INFINITY)
+ assert_equal(1, Date.today <=> -Float::INFINITY)
+ end
+end
+
+class DateExtConversionsTest < ActiveSupport::TestCase
+ def test_to_time_in_current_zone_is_deprecated
+ assert_deprecated(/to_time_in_current_zone/) do
+ Date.new(2012,6,7).to_time_in_current_zone
+ end
+ end
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index b1d1e8ecb4..54bbdbb18f 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -42,7 +42,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_time
assert_equal Time.utc(2005, 2, 21, 10, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
- assert_equal Time.utc_time(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0).to_time
+ assert_equal Time.utc(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0).to_time
# DateTimes with offsets other than 0 are returned unaltered
assert_equal DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)), DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).to_time
# Fractional seconds are preserved
@@ -61,6 +61,14 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal 86399,DateTime.civil(2005,1,1,23,59,59).seconds_since_midnight
end
+ def test_seconds_until_end_of_day
+ assert_equal 0, DateTime.civil(2005,1,1,23,59,59).seconds_until_end_of_day
+ assert_equal 1, DateTime.civil(2005,1,1,23,59,58).seconds_until_end_of_day
+ assert_equal 60, DateTime.civil(2005,1,1,23,58,59).seconds_until_end_of_day
+ assert_equal 3660, DateTime.civil(2005,1,1,22,58,59).seconds_until_end_of_day
+ assert_equal 86399, DateTime.civil(2005,1,1,0,0,0).seconds_until_end_of_day
+ end
+
def test_beginning_of_day
assert_equal DateTime.civil(2005,2,4,0,0,0), DateTime.civil(2005,2,4,10,10,10).beginning_of_day
end
@@ -309,3 +317,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
end
+
+class DateTimeExtBehaviorTest < ActiveSupport::TestCase
+ def test_compare_with_infinity
+ assert_equal(-1, DateTime.now <=> Float::INFINITY)
+ assert_equal(1, DateTime.now <=> -Float::INFINITY)
+ end
+end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index c8312aa653..5e3987265b 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -21,7 +21,7 @@ class DurationTest < ActiveSupport::TestCase
assert ActiveSupport::Duration === 1.day
assert !(ActiveSupport::Duration === 1.day.to_i)
assert !(ActiveSupport::Duration === 'foo')
- assert !(ActiveSupport::Duration === ActiveSupport::BasicObject.new)
+ assert !(ActiveSupport::Duration === ActiveSupport::ProxyObject.new)
end
def test_equals
@@ -50,14 +50,12 @@ class DurationTest < ActiveSupport::TestCase
end
def test_argument_error
- begin
- 1.second.ago('')
- flunk("no exception was raised")
- rescue ArgumentError => e
- assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb"
- rescue Exception => e
- flunk("ArgumentError should be raised, but we got #{e.class} instead")
- end
+ 1.second.ago('')
+ flunk("no exception was raised")
+ rescue ArgumentError => e
+ assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb"
+ rescue Exception => e
+ flunk("ArgumentError should be raised, but we got #{e.class} instead")
end
def test_fractional_weeks
@@ -131,7 +129,7 @@ class DurationTest < ActiveSupport::TestCase
assert_equal Time.local(2009,3,29,0,0,0) + 1.day, Time.local(2009,3,30,0,0,0)
end
end
-
+
def test_delegation_with_block_works
counter = 0
assert_nothing_raised do
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index c378dcd01d..5fc81ba6fc 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -1330,7 +1330,7 @@ class HashToXmlTest < ActiveSupport::TestCase
def test_empty_string_works_for_typecast_xml_value
assert_nothing_raised do
- Hash.__send__(:typecast_xml_value, "")
+ ActiveSupport::XMLConverter.new("").to_h
end
end
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
new file mode 100644
index 0000000000..ac79b15fa8
--- /dev/null
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -0,0 +1,124 @@
+require 'abstract_unit'
+require 'active_support/core_ext/marshal'
+require 'dependecies_test_helpers'
+
+class MarshalTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include DependeciesTestHelpers
+
+ def teardown
+ ActiveSupport::Dependencies.clear
+ remove_constants(:E, :ClassFolder)
+ end
+
+ test "that Marshal#load still works" do
+ sanity_data = ["test", [1, 2, 3], {a: [1, 2, 3]}, ActiveSupport::TestCase]
+ sanity_data.each do |obj|
+ dumped = Marshal.dump(obj)
+ assert_equal Marshal.load_without_autoloading(dumped), Marshal.load(dumped)
+ end
+ end
+
+ test "that a missing class is autoloaded from string" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump(E.new)
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of E, Marshal.load(dumped)
+ end
+ end
+
+ test "that classes in sub modules work" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump(ClassFolder::ClassFolderSubclass.new)
+ end
+
+ remove_constants(:ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of ClassFolder::ClassFolderSubclass, Marshal.load(dumped)
+ end
+ end
+
+ test "that more than one missing class is autoloaded" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump([E.new, ClassFolder.new])
+ end
+
+ remove_constants(:E, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ loaded = Marshal.load(dumped)
+ assert_equal 2, loaded.size
+ assert_kind_of E, loaded[0]
+ assert_kind_of ClassFolder, loaded[1]
+ end
+ end
+
+ test "that a real missing class is causing an exception" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump(E.new)
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ assert_raise(NameError) do
+ Marshal.load(dumped)
+ end
+ end
+
+ test "when first class is autoloaded and second not" do
+ dumped = nil
+ class SomeClass
+ end
+
+ with_autoloading_fixtures do
+ dumped = Marshal.dump([E.new, SomeClass.new])
+ end
+
+ remove_constants(:E)
+ self.class.send(:remove_const, :SomeClass)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_raise(NameError) do
+ Marshal.load(dumped)
+ end
+
+ assert_nothing_raised("E failed to load while we expect only SomeClass to fail loading") do
+ E.new
+ end
+
+ assert_raise(NameError, "We expected SomeClass to not be loaded but it is!") do
+ SomeClass.new
+ end
+ end
+ end
+
+ test "loading classes from files trigger autoloading" do
+ Tempfile.open("object_serializer_test") do |f|
+ with_autoloading_fixtures do
+ Marshal.dump(E.new, f)
+ end
+
+ f.rewind
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of E, Marshal.load(f)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 435f4aa5a1..8c7d00cae1 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -203,7 +203,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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))
@@ -217,7 +217,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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))
@@ -228,8 +228,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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))
@@ -246,7 +246,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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))
@@ -274,7 +274,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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 => ',')
@@ -350,7 +350,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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)
@@ -366,13 +366,13 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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)
@@ -436,7 +436,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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)
@@ -447,3 +447,57 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
end
+
+class NumericExtBehaviorTest < ActiveSupport::TestCase
+ def setup
+ @inf = BigDecimal.new('Infinity')
+ end
+
+ def test_compare_infinity_with_date
+ assert_equal(-1, -Float::INFINITY <=> Date.today)
+ assert_equal(1, Float::INFINITY <=> Date.today)
+ assert_equal(-1, -@inf <=> Date.today)
+ assert_equal(1, @inf <=> Date.today)
+ end
+
+ def test_compare_infinty_with_infinty
+ assert_equal(-1, -Float::INFINITY <=> Float::INFINITY)
+ assert_equal(1, Float::INFINITY <=> -Float::INFINITY)
+ assert_equal(0, Float::INFINITY <=> Float::INFINITY)
+ assert_equal(0, -Float::INFINITY <=> -Float::INFINITY)
+
+ assert_equal(-1, -Float::INFINITY <=> BigDecimal::INFINITY)
+ assert_equal(1, Float::INFINITY <=> -BigDecimal::INFINITY)
+ assert_equal(0, Float::INFINITY <=> BigDecimal::INFINITY)
+ assert_equal(0, -Float::INFINITY <=> -BigDecimal::INFINITY)
+
+ assert_equal(-1, -BigDecimal::INFINITY <=> Float::INFINITY)
+ assert_equal(1, BigDecimal::INFINITY <=> -Float::INFINITY)
+ assert_equal(0, BigDecimal::INFINITY <=> Float::INFINITY)
+ assert_equal(0, -BigDecimal::INFINITY <=> -Float::INFINITY)
+ end
+
+ def test_compare_infinity_with_time
+ assert_equal(-1, -Float::INFINITY <=> Time.now)
+ assert_equal(1, Float::INFINITY <=> Time.now)
+ assert_equal(-1, -@inf <=> Time.now)
+ assert_equal(1, @inf <=> Time.now)
+ end
+
+ def test_compare_infinity_with_datetime
+ assert_equal(-1, -Float::INFINITY <=> DateTime.now)
+ assert_equal(1, Float::INFINITY <=> DateTime.now)
+ assert_equal(-1, -@inf <=> DateTime.now)
+ assert_equal(1, @inf <=> DateTime.now)
+ end
+
+ def test_compare_infinity_with_twz
+ time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone)
+
+ assert_equal(-1, -Float::INFINITY <=> twz)
+ assert_equal(1, Float::INFINITY <=> twz)
+ assert_equal(-1, -@inf <=> twz)
+ assert_equal(1, @inf <=> twz)
+ end
+end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index f0cdc0bfd4..0051c48984 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'active_support/core_ext/range'
+require 'active_support/core_ext/numeric'
class RangeTest < ActiveSupport::TestCase
def test_to_s_from_dates
@@ -85,4 +86,28 @@ class RangeTest < ActiveSupport::TestCase
time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00)
assert !time_range_1.overlaps?(time_range_2)
end
+
+ def test_infinite_bounds
+ time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+
+ time = Time.now
+ date = Date.today
+ datetime = DateTime.now
+ twz = ActiveSupport::TimeWithZone.new(time, time_zone)
+
+ infinity1 = Float::INFINITY
+ infinity2 = BigDecimal.new('Infinity')
+
+ [infinity1, infinity2].each do |infinity|
+ [time, date, datetime, twz].each do |bound|
+ [time, date, datetime, twz].each do |value|
+ assert Range.new(bound, infinity).include?(value + 10.years)
+ assert Range.new(-infinity, bound).include?(value - 10.years)
+
+ assert !Range.new(bound, infinity).include?(value - 10.years)
+ assert !Range.new(-infinity, bound).include?(value + 10.years)
+ end
+ end
+ end
+ end
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 6720ed42f0..e0ddeab548 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -161,30 +161,6 @@ class StringInflectionsTest < ActiveSupport::TestCase
assert_equal 97, 'abc'.ord
end
- def test_string_to_time
- assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time
- assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local)
- assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time
- assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local)
- assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time
- assert_equal Time.local_time(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local)
- assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time
- assert_nil "".to_time
- end
-
- def test_string_to_datetime
- assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_datetime
- assert_equal 0, "2039-02-27 23:50".to_datetime.offset # use UTC offset
- assert_equal ::Date::ITALY, "2039-02-27 23:50".to_datetime.start # use Ruby's default start value
- assert_equal DateTime.civil(2039, 2, 27, 23, 50, 19 + Rational(275038, 1000000), "-04:00"), "2039-02-27T23:50:19.275038-04:00".to_datetime
- assert_nil "".to_datetime
- end
-
- def test_string_to_date
- assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date
- assert_nil "".to_date
- end
-
def test_access
s = "hello"
assert_equal "h", s.at(0)
@@ -308,6 +284,33 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+class StringConversionsTest < ActiveSupport::TestCase
+ def test_string_to_time
+ assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time
+ assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local)
+ assert_equal Time.utc(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time
+ assert_equal Time.local(2005, 2, 27, 23, 50, 19, 275038), "2005-02-27T23:50:19.275038".to_time(:local)
+ assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time
+ assert_equal Time.local(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_time(:local)
+ assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time
+ assert_nil "".to_time
+ end
+
+ def test_string_to_datetime
+ assert_equal DateTime.civil(2039, 2, 27, 23, 50), "2039-02-27 23:50".to_datetime
+ assert_equal 0, "2039-02-27 23:50".to_datetime.offset # use UTC offset
+ assert_equal ::Date::ITALY, "2039-02-27 23:50".to_datetime.start # use Ruby's default start value
+ assert_equal DateTime.civil(2039, 2, 27, 23, 50, 19 + Rational(275038, 1000000), "-04:00"), "2039-02-27T23:50:19.275038-04:00".to_datetime
+ assert_nil "".to_datetime
+ end
+
+ def test_string_to_date
+ assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date
+ assert_nil "".to_date
+ assert_equal Date.new(Date.today.year, 2, 3), "Feb 3rd".to_date
+ end
+end
+
class StringBehaviourTest < ActiveSupport::TestCase
def test_acts_like_string
assert 'Bambi'.acts_like_string?
diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb
new file mode 100644
index 0000000000..230c1203ad
--- /dev/null
+++ b/activesupport/test/core_ext/thread_test.rb
@@ -0,0 +1,77 @@
+require 'abstract_unit'
+require 'active_support/core_ext/thread'
+
+class ThreadExt < ActiveSupport::TestCase
+ def test_main_thread_variable_in_enumerator
+ assert_equal Thread.main, Thread.current
+
+ Thread.current.thread_variable_set :foo, "bar"
+
+ thread, value = Fiber.new {
+ Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
+ }.resume
+
+ assert_equal Thread.current, thread
+ assert_equal Thread.current.thread_variable_get(:foo), value
+ end
+
+ def test_thread_variable_in_enumerator
+ Thread.new {
+ Thread.current.thread_variable_set :foo, "bar"
+
+ thread, value = Fiber.new {
+ Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)]
+ }.resume
+
+ assert_equal Thread.current, thread
+ assert_equal Thread.current.thread_variable_get(:foo), value
+ }.join
+ end
+
+ def test_thread_variables
+ assert_equal [], Thread.new { Thread.current.thread_variables }.join.value
+
+ t = Thread.new {
+ Thread.current.thread_variable_set(:foo, "bar")
+ Thread.current.thread_variables
+ }
+ assert_equal [:foo], t.join.value
+ end
+
+ def test_thread_variable?
+ assert_not Thread.new { Thread.current.thread_variable?("foo") }.join.value
+ t = Thread.new {
+ Thread.current.thread_variable_set("foo", "bar")
+ }.join
+
+ assert t.thread_variable?("foo")
+ assert t.thread_variable?(:foo)
+ assert_not t.thread_variable?(:bar)
+ end
+
+ def test_thread_variable_strings_and_symbols_are_the_same_key
+ t = Thread.new {}.join
+ t.thread_variable_set("foo", "bar")
+ assert_equal "bar", t.thread_variable_get(:foo)
+ end
+
+ def test_thread_variable_frozen
+ t = Thread.new { }.join
+ t.freeze
+ assert_raises(RuntimeError) do
+ t.thread_variable_set(:foo, "bar")
+ end
+ end
+
+ def test_thread_variable_security
+ t = Thread.new { sleep }
+
+ assert_raises(SecurityError) do
+ Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join
+ end
+
+ assert_raises(SecurityError) do
+ Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 6d6757a1b6..a2fefee3b8 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -57,6 +57,54 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_seconds_until_end_of_day
+ assert_equal 0, Time.local(2005,1,1,23,59,59).seconds_until_end_of_day
+ assert_equal 1, Time.local(2005,1,1,23,59,58).seconds_until_end_of_day
+ assert_equal 60, Time.local(2005,1,1,23,58,59).seconds_until_end_of_day
+ assert_equal 3660, Time.local(2005,1,1,22,58,59).seconds_until_end_of_day
+ assert_equal 86399, Time.local(2005,1,1,0,0,0).seconds_until_end_of_day
+ end
+
+ def test_seconds_until_end_of_day_at_daylight_savings_time_start
+ with_env_tz 'US/Eastern' do
+ # dt: US: 2005 April 3rd 2:00am ST => April 3rd 3:00am DT
+ assert_equal 21*3600, Time.local(2005,4,3,1,59,59).seconds_until_end_of_day, 'just before DST start'
+ assert_equal 21*3600-2, Time.local(2005,4,3,3,0,1).seconds_until_end_of_day, 'just after DST start'
+ end
+
+ with_env_tz 'NZ' do
+ # dt: New Zealand: 2006 October 1st 2:00am ST => October 1st 3:00am DT
+ assert_equal 21*3600, Time.local(2006,10,1,1,59,59).seconds_until_end_of_day, 'just before DST start'
+ assert_equal 21*3600-2, Time.local(2006,10,1,3,0,1).seconds_until_end_of_day, 'just after DST start'
+ end
+ end
+
+ def test_seconds_until_end_of_day_at_daylight_savings_time_end
+ with_env_tz 'US/Eastern' do
+ # st: US: 2005 October 30th 2:00am DT => October 30th 1:00am ST
+ # avoid setting a time between 1:00 and 2:00 since that requires specifying whether DST is active
+ assert_equal 24*3600, Time.local(2005,10,30,0,59,59).seconds_until_end_of_day, 'just before DST end'
+ assert_equal 22*3600-2, Time.local(2005,10,30,2,0,1).seconds_until_end_of_day, 'just after DST end'
+
+ # now set a time between 1:00 and 2:00 by specifying whether DST is active
+ # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz )
+ assert_equal 24*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,true,ENV['TZ']).seconds_until_end_of_day, 'before DST end'
+ assert_equal 23*3600-30*60-1, Time.local(0,30,1,30,10,2005,0,0,false,ENV['TZ']).seconds_until_end_of_day, 'after DST end'
+ end
+
+ with_env_tz 'NZ' do
+ # st: New Zealand: 2006 March 19th 3:00am DT => March 19th 2:00am ST
+ # avoid setting a time between 2:00 and 3:00 since that requires specifying whether DST is active
+ assert_equal 23*3600, Time.local(2006,3,19,1,59,59).seconds_until_end_of_day, 'just before DST end'
+ assert_equal 21*3600-2, Time.local(2006,3,19,3,0,1).seconds_until_end_of_day, 'just after DST end'
+
+ # now set a time between 2:00 and 3:00 by specifying whether DST is active
+ # uses: Time.local( sec, min, hour, day, month, year, wday, yday, isdst, tz )
+ assert_equal 23*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,true, ENV['TZ']).seconds_until_end_of_day, 'before DST end'
+ assert_equal 22*3600-30*60-1, Time.local(0,30,2,19,3,2006,0,0,false,ENV['TZ']).seconds_until_end_of_day, 'after DST end'
+ end
+ end
+
def test_beginning_of_day
assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day
with_env_tz 'US/Eastern' do
@@ -519,45 +567,57 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_time_with_datetime_fallback
- assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30)
- assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
- assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0)
- assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
- assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0)
- assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005)
- assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0)
- assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec
- # This won't overflow on 64bit linux
- unless time_is_64bits?
- assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30)
- assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1),
- DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
- assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
- end
- silence_warnings do
- 0.upto(138) do |year|
- [:utc, :local].each do |format|
- assert_equal year, Time.time_with_datetime_fallback(format, year).year
+ ActiveSupport::Deprecation.silence do
+ assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30)
+ assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0)
+ assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
+ assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec
+ # This won't overflow on 64bit linux
+ unless time_is_64bits?
+ assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1),
+ DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
+ assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
+ end
+ silence_warnings do
+ 0.upto(138) do |year|
+ [:utc, :local].each do |format|
+ assert_equal year, Time.time_with_datetime_fallback(format, year).year
+ end
end
end
end
end
def test_utc_time
- assert_equal Time.utc_time(2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30)
- assert_equal Time.utc_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0)
- assert_equal Time.utc_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, 0)
+ ActiveSupport::Deprecation.silence do
+ assert_equal Time.utc_time(2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30)
+ assert_equal Time.utc_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0)
+ assert_equal Time.utc_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, 0)
+ end
end
def test_local_time
- assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
- assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
+ ActiveSupport::Deprecation.silence do
+ assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
+ assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
- unless time_is_64bits?
- assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30)
+ unless time_is_64bits?
+ assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30)
+ end
end
end
+ def test_time_with_datetime_fallback_deprecations
+ assert_deprecated(/time_with_datetime_fallback/) { Time.time_with_datetime_fallback(:utc, 2012, 6, 7) }
+ assert_deprecated(/utc_time/) { Time.utc_time(2012, 6, 7) }
+ assert_deprecated(/local_time/) { Time.local_time(2012, 6, 7) }
+ end
+
def test_last_month_on_31st
assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month
end
@@ -783,3 +843,10 @@ class TimeExtMarshalingTest < ActiveSupport::TestCase
assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter
end
end
+
+class TimeExtBehaviorTest < ActiveSupport::TestCase
+ def test_compare_with_infinity
+ assert_equal(-1, Time.now <=> Float::INFINITY)
+ assert_equal(1, Time.now <=> -Float::INFINITY)
+ end
+end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 1293f104e5..6c773770f0 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -953,3 +953,141 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
end
+
+class TimeWithZoneMethodsForDate < ActiveSupport::TestCase
+ def setup
+ @d = Date.civil(2000)
+ end
+
+ def teardown
+ Time.zone = nil
+ end
+
+ def test_in_time_zone
+ with_tz_default 'Alaska' do
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone.inspect
+ end
+ with_tz_default 'Hawaii' do
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone.inspect
+ end
+ with_tz_default nil do
+ assert_equal @d.to_time, @d.in_time_zone
+ end
+ end
+
+ def test_nil_time_zone
+ with_tz_default nil do
+ assert !@d.in_time_zone.respond_to?(:period), 'no period method'
+ end
+ end
+
+ def test_in_time_zone_with_argument
+ with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone)
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone('Alaska').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @d.in_time_zone('Hawaii').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @d.in_time_zone('UTC').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @d.in_time_zone(-9.hours).inspect
+ end
+ end
+
+ def test_in_time_zone_with_invalid_argument
+ assert_raise(ArgumentError) { @d.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @d.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @d.in_time_zone(Object.new) }
+ end
+
+ protected
+ def with_tz_default(tz = nil)
+ old_tz = Time.zone
+ Time.zone = tz
+ yield
+ ensure
+ Time.zone = old_tz
+ end
+end
+
+class TimeWithZoneMethodsForString < ActiveSupport::TestCase
+ def setup
+ @s = "Sat, 01 Jan 2000 00:00:00"
+ @u = "Sat, 01 Jan 2000 00:00:00 UTC +00:00"
+ @z = "Fri, 31 Dec 1999 19:00:00 EST -05:00"
+ end
+
+ def teardown
+ Time.zone = nil
+ end
+
+ def test_in_time_zone
+ with_tz_default 'Alaska' do
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone.inspect
+ assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone.inspect
+ assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone.inspect
+ end
+ with_tz_default 'Hawaii' do
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone.inspect
+ assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone.inspect
+ assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone.inspect
+ end
+ with_tz_default nil do
+ assert_equal @s.to_time, @s.in_time_zone
+ assert_equal @u.to_time, @u.in_time_zone
+ assert_equal @z.to_time, @z.in_time_zone
+ end
+ end
+
+ def test_nil_time_zone
+ with_tz_default nil do
+ assert !@s.in_time_zone.respond_to?(:period), 'no period method'
+ assert !@u.in_time_zone.respond_to?(:period), 'no period method'
+ assert !@z.in_time_zone.respond_to?(:period), 'no period method'
+ end
+ end
+
+ def test_in_time_zone_with_argument
+ with_tz_default 'Eastern Time (US & Canada)' do # Time.zone will not affect #in_time_zone(zone)
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone('Alaska').inspect
+ assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone('Alaska').inspect
+ assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone('Alaska').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 HST -10:00', @s.in_time_zone('Hawaii').inspect
+ assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @u.in_time_zone('Hawaii').inspect
+ assert_equal 'Fri, 31 Dec 1999 14:00:00 HST -10:00', @z.in_time_zone('Hawaii').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @s.in_time_zone('UTC').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @u.in_time_zone('UTC').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 UTC +00:00', @z.in_time_zone('UTC').inspect
+ assert_equal 'Sat, 01 Jan 2000 00:00:00 AKST -09:00', @s.in_time_zone(-9.hours).inspect
+ assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @u.in_time_zone(-9.hours).inspect
+ assert_equal 'Fri, 31 Dec 1999 15:00:00 AKST -09:00', @z.in_time_zone(-9.hours).inspect
+ end
+ end
+
+ def test_in_time_zone_with_invalid_argument
+ assert_raise(ArgumentError) { @s.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @u.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @z.in_time_zone("No such timezone exists") }
+ assert_raise(ArgumentError) { @s.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @u.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @z.in_time_zone(-15.hours) }
+ assert_raise(ArgumentError) { @s.in_time_zone(Object.new) }
+ assert_raise(ArgumentError) { @u.in_time_zone(Object.new) }
+ assert_raise(ArgumentError) { @z.in_time_zone(Object.new) }
+ end
+
+ protected
+ def with_tz_default(tz = nil)
+ old_tz = Time.zone
+ Time.zone = tz
+ yield
+ ensure
+ Time.zone = old_tz
+ end
+end
+
+class TimeWithZoneExtBehaviorTest < ActiveSupport::TestCase
+ def test_compare_with_infinity
+ time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone)
+
+ assert_equal(-1, twz <=> Float::INFINITY)
+ assert_equal(1, twz <=> -Float::INFINITY)
+ end
+end
diff --git a/activesupport/test/dependecies_test_helpers.rb b/activesupport/test/dependecies_test_helpers.rb
new file mode 100644
index 0000000000..4b46d32fb4
--- /dev/null
+++ b/activesupport/test/dependecies_test_helpers.rb
@@ -0,0 +1,27 @@
+module DependeciesTestHelpers
+ def with_loading(*from)
+ old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
+ this_dir = File.dirname(__FILE__)
+ parent_dir = File.dirname(this_dir)
+ path_copy = $LOAD_PATH.dup
+ $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
+ prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths
+ ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" }
+ yield
+ ensure
+ $LOAD_PATH.replace(path_copy)
+ ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
+ ActiveSupport::Dependencies.mechanism = old_mechanism
+ ActiveSupport::Dependencies.explicitly_unloadable_constants = []
+ end
+
+ def with_autoloading_fixtures(&block)
+ with_loading 'autoloading_fixtures', &block
+ end
+
+ def remove_constants(*constants)
+ constants.each do |constant|
+ Object.send(:remove_const, constant) if Object.const_defined?(constant)
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 67bd6669c5..615808090d 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'pp'
require 'active_support/dependencies'
+require 'dependecies_test_helpers'
module ModuleWithMissing
mattr_accessor :missing_count
@@ -19,25 +20,7 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
end
- def with_loading(*from)
- old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
- this_dir = File.dirname(__FILE__)
- parent_dir = File.dirname(this_dir)
- path_copy = $LOAD_PATH.dup
- $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
- prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths
- ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" }
- yield
- ensure
- $LOAD_PATH.replace(path_copy)
- ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
- ActiveSupport::Dependencies.mechanism = old_mechanism
- ActiveSupport::Dependencies.explicitly_unloadable_constants = []
- end
-
- def with_autoloading_fixtures(&block)
- with_loading 'autoloading_fixtures', &block
- end
+ include DependeciesTestHelpers
def test_depend_on_path
skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0'
@@ -380,12 +363,10 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_smart_name_error_strings
- begin
- Object.module_eval "ImaginaryObject"
- flunk "No raise!!"
- rescue NameError => e
- assert e.message.include?("uninitialized constant ImaginaryObject")
- end
+ Object.module_eval "ImaginaryObject"
+ flunk "No raise!!"
+ rescue NameError => e
+ assert e.message.include?("uninitialized constant ImaginaryObject")
end
def test_loadable_constants_for_path_should_handle_empty_autoloads
@@ -938,10 +919,20 @@ class DependenciesTest < ActiveSupport::TestCase
assert !defined?(ShouldNotBeAutoloaded)
end
+ def test_remove_constant_does_not_autoload_already_removed_parents_as_a_side_effect
+ with_autoloading_fixtures do
+ _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context"
+ _ = ::A::B # assignment to silence parse-time warning "possibly useless use of :: in void context"
+ ActiveSupport::Dependencies.remove_constant('A')
+ ActiveSupport::Dependencies.remove_constant('A::B')
+ assert !defined?(A)
+ end
+ end
+
def test_load_once_constants_should_not_be_unloaded
with_autoloading_fixtures do
ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths
- ::A.to_s
+ _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context"
assert defined?(A)
ActiveSupport::Dependencies.clear
assert defined?(A)
diff --git a/activesupport/test/deprecation/basic_object_test.rb b/activesupport/test/deprecation/basic_object_test.rb
new file mode 100644
index 0000000000..4b5bed9eb1
--- /dev/null
+++ b/activesupport/test/deprecation/basic_object_test.rb
@@ -0,0 +1,12 @@
+require 'abstract_unit'
+require 'active_support/deprecation'
+require 'active_support/basic_object'
+
+
+class BasicObjectTest < ActiveSupport::TestCase
+ test 'BasicObject warns about deprecation when inherited from' do
+ warn = 'ActiveSupport::BasicObject is deprecated! Use ActiveSupport::ProxyObject instead.'
+ ActiveSupport::Deprecation.expects(:warn).with(warn).once
+ Class.new(ActiveSupport::BasicObject)
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/deprecation/buffered_logger_test.rb b/activesupport/test/deprecation/buffered_logger_test.rb
new file mode 100644
index 0000000000..bf11a4732c
--- /dev/null
+++ b/activesupport/test/deprecation/buffered_logger_test.rb
@@ -0,0 +1,22 @@
+require 'abstract_unit'
+require 'active_support/buffered_logger'
+
+class BufferedLoggerTest < ActiveSupport::TestCase
+
+ def test_can_be_subclassed
+ warn = 'ActiveSupport::BufferedLogger is deprecated! Use ActiveSupport::Logger instead.'
+
+ ActiveSupport::Deprecation.expects(:warn).with(warn).once
+
+ Class.new(ActiveSupport::BufferedLogger)
+ end
+
+ def test_issues_deprecation_when_instantiated
+ warn = 'ActiveSupport::BufferedLogger is deprecated! Use ActiveSupport::Logger instead.'
+
+ ActiveSupport::Deprecation.expects(:warn).with(warn).once
+
+ ActiveSupport::BufferedLogger.new(STDOUT)
+ end
+
+end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 332100f5a1..c1a468ec86 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -119,9 +119,10 @@ class DeprecationTest < ActiveSupport::TestCase
ActiveSupport::Deprecation.behavior = :silence
behavior = ActiveSupport::Deprecation.behavior.first
- assert_blank capture(:stderr) {
+ stderr_output = capture(:stderr) {
assert_nil behavior.call('Some error!', ['call stack!'])
}
+ assert stderr_output.blank?
end
def test_deprecated_instance_variable_proxy
@@ -254,10 +255,10 @@ class DeprecationTest < ActiveSupport::TestCase
klass::OLD.to_s
end
end
-
+
def test_deprecated_instance_variable_with_instance_deprecator
deprecator = deprecator_with_messages
-
+
klass = Class.new() do
def initialize(deprecator)
@request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index aa41e57928..a1e5db6a2e 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -361,7 +361,7 @@ class InflectorTest < ActiveSupport::TestCase
inflect.singular(/s$/, '')
inflect.singular(/es$/, '')
-
+
inflect.irregular('el', 'los')
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 5bb2a45c87..12ce250eb3 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -112,21 +112,34 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_utf8_string_encoded_properly
result = ActiveSupport::JSON.encode('€2.99')
- assert_equal '"\\u20ac2.99"', result
+ assert_equal '"€2.99"', result
assert_equal(Encoding::UTF_8, result.encoding)
result = ActiveSupport::JSON.encode('✎☺')
- assert_equal '"\\u270e\\u263a"', result
+ assert_equal '"✎☺"', result
assert_equal(Encoding::UTF_8, result.encoding)
end
def test_non_utf8_string_transcodes
s = '二'.encode('Shift_JIS')
result = ActiveSupport::JSON.encode(s)
- assert_equal '"\\u4e8c"', result
+ assert_equal '"二"', result
assert_equal Encoding::UTF_8, result.encoding
end
+ def test_wide_utf8_chars
+ w = '𠜎'
+ result = ActiveSupport::JSON.encode(w)
+ assert_equal '"𠜎"', result
+ end
+
+ def test_wide_utf8_roundtrip
+ hash = { string: "𐒑" }
+ json = ActiveSupport::JSON.encode(hash)
+ decoded_hash = ActiveSupport::JSON.decode(json)
+ assert_equal "𐒑", decoded_hash['string']
+ end
+
def test_exception_raised_when_encoding_circular_reference_in_array
a = [1]
a << a
@@ -263,7 +276,8 @@ class TestJSONEncoding < ActiveSupport::TestCase
f.bar = "world"
hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}}
- assert_equal(%({"foo":{"foo":"hello","bar":"world"},"other_hash":{"foo":"other_foo","test":"other_test"}}), hash.to_json)
+ assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"},
+ "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, JSON.parse(hash.to_json))
end
def test_struct_encoding
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index eedeca30a8..d2801849ca 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -120,4 +120,14 @@ class LoggerTest < ActiveSupport::TestCase
byte_string.force_encoding("ASCII-8BIT")
assert byte_string.include?(BYTE_STRING)
end
+
+ def test_silencing_everything_but_errors
+ @logger.silence do
+ @logger.debug "NOT THERE"
+ @logger.error "THIS IS HERE"
+ end
+
+ assert !@output.string.include?("NOT THERE")
+ assert @output.string.include?("THIS IS HERE")
+ end
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index ef289692bc..2bf73291a2 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -47,7 +47,10 @@ class MultibyteCharsTest < ActiveSupport::TestCase
end
def test_methods_are_forwarded_to_wrapped_string_for_byte_strings
- assert_equal BYTE_STRING.class, BYTE_STRING.mb_chars.class
+ original_encoding = BYTE_STRING.encoding
+ assert_equal BYTE_STRING.length, BYTE_STRING.mb_chars.length
+ ensure
+ BYTE_STRING.force_encoding(original_encoding)
end
def test_forwarded_method_with_non_string_result_should_be_returned_vertabim
@@ -673,6 +676,9 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
assert_equal "𥤤", chars(byte_string).tidy_bytes(true)
end
+ def test_class_is_not_forwarded
+ assert_equal BYTE_STRING.dup.mb_chars.class, ActiveSupport::Multibyte::Chars
+ end
private
diff --git a/activesupport/test/queueing/synchronous_queue_test.rb b/activesupport/test/queueing/synchronous_queue_test.rb
deleted file mode 100644
index 86c39d0f6c..0000000000
--- a/activesupport/test/queueing/synchronous_queue_test.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'abstract_unit'
-require 'active_support/queueing'
-
-class SynchronousQueueTest < ActiveSupport::TestCase
- class Job
- attr_reader :ran
- def run; @ran = true end
- end
-
- class ExceptionRaisingJob
- def run; raise end
- end
-
- def setup
- @queue = ActiveSupport::SynchronousQueue.new
- end
-
- def test_runs_jobs_immediately
- job = Job.new
- @queue.push job
- assert job.ran
-
- assert_raises RuntimeError do
- @queue.push ExceptionRaisingJob.new
- end
- end
-end
diff --git a/activesupport/test/queueing/test_queue_test.rb b/activesupport/test/queueing/test_queue_test.rb
deleted file mode 100644
index 451fb68d3e..0000000000
--- a/activesupport/test/queueing/test_queue_test.rb
+++ /dev/null
@@ -1,146 +0,0 @@
-require 'abstract_unit'
-require 'active_support/queueing'
-
-class TestQueueTest < ActiveSupport::TestCase
- def setup
- @queue = ActiveSupport::TestQueue.new
- end
-
- class ExceptionRaisingJob
- def run
- raise
- end
- end
-
- def test_drain_raises_exceptions_from_running_jobs
- @queue.push ExceptionRaisingJob.new
- assert_raises(RuntimeError) { @queue.drain }
- end
-
- def test_jobs
- @queue.push 1
- @queue.push 2
- assert_equal [1,2], @queue.jobs
- end
-
- class EquivalentJob
- def initialize
- @initial_id = self.object_id
- end
-
- def run
- end
-
- def ==(other)
- other.same_initial_id?(@initial_id)
- end
-
- def same_initial_id?(other_id)
- other_id == @initial_id
- end
- end
-
- def test_contents
- job = EquivalentJob.new
- assert @queue.empty?
- @queue.push job
- refute @queue.empty?
- assert_equal job, @queue.pop
- end
-
- class ProcessingJob
- def self.clear_processed
- @processed = []
- end
-
- def self.processed
- @processed
- end
-
- def initialize(object)
- @object = object
- end
-
- def run
- self.class.processed << @object
- end
- end
-
- def test_order
- ProcessingJob.clear_processed
- job1 = ProcessingJob.new(1)
- job2 = ProcessingJob.new(2)
-
- @queue.push job1
- @queue.push job2
- @queue.drain
-
- assert_equal [1,2], ProcessingJob.processed
- end
-
- class ThreadTrackingJob
- attr_reader :thread_id
-
- def run
- @thread_id = Thread.current.object_id
- end
-
- def ran?
- @thread_id
- end
- end
-
- def test_drain
- @queue.push ThreadTrackingJob.new
- job = @queue.jobs.last
- @queue.drain
-
- assert @queue.empty?
- assert job.ran?, "The job runs synchronously when the queue is drained"
- assert_equal job.thread_id, Thread.current.object_id
- end
-
- class IdentifiableJob
- def initialize(id)
- @id = id
- end
-
- def ==(other)
- other.same_id?(@id)
- end
-
- def same_id?(other_id)
- other_id == @id
- end
-
- def run
- end
- end
-
- def test_queue_can_be_observed
- jobs = (1..10).map do |id|
- IdentifiableJob.new(id)
- end
-
- jobs.each do |job|
- @queue.push job
- end
-
- assert_equal jobs, @queue.jobs
- end
-
- def test_adding_an_unmarshallable_job
- anonymous_class_instance = Struct.new(:run).new
-
- assert_raises TypeError do
- @queue.push anonymous_class_instance
- end
- end
-
- def test_attempting_to_add_a_reference_to_itself
- job = {reference: @queue}
- assert_raises TypeError do
- @queue.push job
- end
- end
-end
diff --git a/activesupport/test/queueing/threaded_consumer_test.rb b/activesupport/test/queueing/threaded_consumer_test.rb
deleted file mode 100644
index a3ca46a261..0000000000
--- a/activesupport/test/queueing/threaded_consumer_test.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'abstract_unit'
-require 'active_support/queueing'
-require "active_support/log_subscriber/test_helper"
-
-class TestThreadConsumer < ActiveSupport::TestCase
- class Job
- attr_reader :id
- def initialize(id = 1, &block)
- @id = id
- @block = block
- end
-
- def run
- @block.call if @block
- end
- end
-
- def setup
- @logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
- @queue = ActiveSupport::Queue.new(logger: @logger)
- end
-
- def teardown
- @queue.drain
- end
-
- test "the jobs are executed" do
- ran = false
- job = Job.new { ran = true }
-
- @queue.push job
- @queue.drain
-
- assert_equal true, ran
- end
-
- test "the jobs are not executed synchronously" do
- run, ran = Queue.new, Queue.new
- job = Job.new { ran.push run.pop }
-
- @queue.consumer.start
- @queue.push job
- assert ran.empty?
-
- run.push true
- assert_equal true, ran.pop
- end
-
- test "shutting down the queue synchronously drains the jobs" do
- ran = false
- job = Job.new do
- sleep 0.1
- ran = true
- end
-
- @queue.consumer.start
- @queue.push job
- assert_equal false, ran
-
- @queue.consumer.shutdown
- assert_equal true, ran
- end
-
- test "log job that raises an exception" do
- job = Job.new { raise "RuntimeError: Error!" }
-
- @queue.push job
- consume_queue @queue
-
- assert_equal 1, @logger.logged(:error).size
- assert_match "Job Error: #{job.inspect}\nRuntimeError: Error!", @logger.logged(:error).last
- end
-
- test "logger defaults to stderr" do
- begin
- $stderr, old_stderr = StringIO.new, $stderr
- queue = ActiveSupport::Queue.new
- queue.push Job.new { raise "RuntimeError: Error!" }
- consume_queue queue
- assert_match 'Job Error', $stderr.string
- ensure
- $stderr = old_stderr
- end
- end
-
- test "test overriding exception handling" do
- @queue.consumer.instance_eval do
- def handle_exception(job, exception)
- @last_error = exception.message
- end
-
- def last_error
- @last_error
- end
- end
-
- job = Job.new { raise "RuntimeError: Error!" }
-
- @queue.push job
- consume_queue @queue
-
- assert_equal "RuntimeError: Error!", @queue.consumer.last_error
- end
-
- private
- def consume_queue(queue)
- queue.push nil
- queue.consumer.consume
- end
-end
diff --git a/activesupport/test/spec_type_test.rb b/activesupport/test/spec_type_test.rb
deleted file mode 100644
index 9a6cb4ded2..0000000000
--- a/activesupport/test/spec_type_test.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require "abstract_unit"
-require "active_record"
-
-class SomeRandomModel < ActiveRecord::Base; end
-
-class SpecTypeTest < ActiveSupport::TestCase
- def assert_support actual
- assert_equal ActiveSupport::TestCase, actual
- end
-
- def assert_spec actual
- assert_equal MiniTest::Spec, actual
- end
-
- def test_spec_type_resolves_for_active_record_constants
- assert_support MiniTest::Spec.spec_type(SomeRandomModel)
- end
-
- def test_spec_type_doesnt_resolve_random_strings
- assert_spec MiniTest::Spec.spec_type("Unmatched String")
- end
-end
diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb
index 94d5fe197d..a2ed577eb0 100644
--- a/activesupport/test/string_inquirer_test.rb
+++ b/activesupport/test/string_inquirer_test.rb
@@ -10,7 +10,7 @@ class StringInquirerTest < ActiveSupport::TestCase
end
def test_miss
- refute @string_inquirer.development?
+ assert_not @string_inquirer.development?
end
def test_missing_question_mark
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 64426d02e9..dfe9f3c11c 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -108,5 +108,11 @@ module ActiveSupport
test = tc.new test_name
assert_raises(Interrupt) { test.run fr }
end
+
+ def test_pending_deprecation
+ assert_deprecated do
+ pending "should use #skip instead"
+ end
+ end
end
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 9516556844..3e6ac811a4 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -15,6 +15,17 @@ class AssertDifferenceTest < ActiveSupport::TestCase
@object.num = 0
end
+ def test_assert_not
+ assert_equal true, assert_not(nil)
+ assert_equal true, assert_not(false)
+
+ e = assert_raises(MiniTest::Assertion) { assert_not true }
+ assert_equal 'Expected true to be nil or false', e.message
+
+ e = assert_raises(MiniTest::Assertion) { assert_not true, 'custom' }
+ assert_equal 'custom', e.message
+ end
+
def test_assert_no_difference
assert_no_difference '@object.num' do
# ...
@@ -81,17 +92,21 @@ class AssertBlankTest < ActiveSupport::TestCase
NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ]
def test_assert_blank_true
- BLANK.each { |v| assert_blank v }
+ BLANK.each { |value|
+ assert_deprecated { assert_blank value }
+ }
end
def test_assert_blank_false
NOT_BLANK.each { |v|
- begin
- assert_blank v
- fail 'should not get to here'
- rescue Exception => e
- assert_match(/is not blank/, e.message)
- end
+ assert_deprecated {
+ begin
+ assert_blank v
+ fail 'should not get to here'
+ rescue Exception => e
+ assert_match(/is not blank/, e.message)
+ end
+ }
}
end
end
@@ -101,17 +116,21 @@ class AssertPresentTest < ActiveSupport::TestCase
NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ]
def test_assert_present_true
- NOT_BLANK.each { |v| assert_present v }
+ NOT_BLANK.each { |v|
+ assert_deprecated { assert_present v }
+ }
end
def test_assert_present_false
BLANK.each { |v|
- begin
- assert_present v
- fail 'should not get to here'
- rescue Exception => e
- assert_match(/is blank/, e.message)
- end
+ assert_deprecated {
+ begin
+ assert_present v
+ fail 'should not get to here'
+ rescue Exception => e
+ assert_match(/is blank/, e.message)
+ end
+ }
}
end
end
@@ -122,12 +141,12 @@ end
# Setup and teardown callbacks.
class SetupAndTeardownTest < ActiveSupport::TestCase
setup :reset_callback_record, :foo
- teardown :foo, :sentinel, :foo
+ teardown :foo, :sentinel
def test_inherited_setup_callbacks
assert_equal [:reset_callback_record, :foo], self.class._setup_callbacks.map(&:raw_filter)
assert_equal [:foo], @called_back
- assert_equal [:foo, :sentinel, :foo], self.class._teardown_callbacks.map(&:raw_filter)
+ assert_equal [:foo, :sentinel], self.class._teardown_callbacks.map(&:raw_filter)
end
def setup
@@ -147,7 +166,7 @@ class SetupAndTeardownTest < ActiveSupport::TestCase
end
def sentinel
- assert_equal [:foo, :foo], @called_back
+ assert_equal [:foo], @called_back
end
end
@@ -159,7 +178,7 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest
def test_inherited_setup_callbacks
assert_equal [:reset_callback_record, :foo, :bar], self.class._setup_callbacks.map(&:raw_filter)
assert_equal [:foo, :bar], @called_back
- assert_equal [:foo, :sentinel, :foo, :bar], self.class._teardown_callbacks.map(&:raw_filter)
+ assert_equal [:foo, :sentinel, :bar], self.class._teardown_callbacks.map(&:raw_filter)
end
protected
@@ -168,7 +187,7 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest
end
def sentinel
- assert_equal [:foo, :bar, :bar, :foo], @called_back
+ assert_equal [:foo, :bar, :bar], @called_back
end
end
@@ -182,7 +201,6 @@ class TestCaseTaggedLoggingTest < ActiveSupport::TestCase
end
def test_logs_tagged_with_current_test_case
- tagged_logger.info 'test'
- assert_equal "[#{self.class.name}] [#{__name__}] test\n", @out.string
+ assert_match "#{self.class}: #{__name__}\n", @out.string
end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index bfd6863e40..9c3b5d0667 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -178,8 +178,8 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse_with_old_date
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- twz = zone.parse('1850-12-31 19:00:00')
- assert_equal [0,0,19,31,12,1850], twz.to_a[0,6]
+ twz = zone.parse('1883-12-31 19:00:00')
+ assert_equal [0,0,19,31,12,1883], twz.to_a[0,6]
assert_equal zone, twz.time_zone
end
@@ -204,21 +204,32 @@ class TimeZoneTest < ActiveSupport::TestCase
end
def test_parse_should_not_black_out_system_timezone_dst_jump
- zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
- zone.stubs(:now).returns(zone.now)
- Time.stubs(:parse).with('2012-03-25 03:29', zone.now).
- returns(Time.local(0,29,4,25,3,2012,nil,nil,true,"+03:00"))
- twz = zone.parse('2012-03-25 03:29')
- assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6]
+ with_env_tz('EET') do
+ zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
+ twz = zone.parse('2012-03-25 03:29:00')
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6]
+ end
end
def test_parse_should_black_out_app_timezone_dst_jump
- zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
- zone.stubs(:now).returns(zone.now)
- Time.stubs(:parse).with('2012-03-11 02:29', zone.now).
- returns(Time.local(0,29,2,11,3,2012,nil,nil,false,"+02:00"))
- twz = zone.parse('2012-03-11 02:29')
- assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6]
+ with_env_tz('EET') do
+ zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
+ twz = zone.parse('2012-03-11 02:29:00')
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6]
+ end
+ end
+
+ def test_parse_with_missing_time_components
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ zone.stubs(:now).returns zone.local(1999, 12, 31, 12, 59, 59)
+ twz = zone.parse('2012-12-01')
+ assert_equal Time.utc(2012, 12, 1), twz.time
+ end
+
+ def test_parse_with_javascript_date
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ twz = zone.parse("Mon May 28 2012 00:00:00 GMT-0700 (PDT)")
+ assert_equal Time.utc(2012, 5, 28, 7, 0, 0), twz.utc
end
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb
index 2c217157d3..294d6595f7 100644
--- a/activesupport/test/ts_isolated.rb
+++ b/activesupport/test/ts_isolated.rb
@@ -1,4 +1,4 @@
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'active_support/test_case'
require 'rbconfig'
require 'active_support/core_ext/kernel/reporting'
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index 5debb2fd59..36ac4161ea 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -1,12 +1,11 @@
-require 'abstract_unit'
-require 'active_support/xml_mini'
-require 'active_support/core_ext/hash/conversions'
-
begin
require 'libxml'
rescue LoadError
# Skip libxml tests
else
+require 'abstract_unit'
+require 'active_support/xml_mini'
+require 'active_support/core_ext/hash/conversions'
class LibxmlEngineTest < ActiveSupport::TestCase
include ActiveSupport
diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
index 94250d48ec..82337961a1 100644
--- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb
+++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
@@ -1,12 +1,11 @@
-require 'abstract_unit'
-require 'active_support/xml_mini'
-require 'active_support/core_ext/hash/conversions'
-
begin
require 'libxml'
rescue LoadError
# Skip libxml tests
else
+require 'abstract_unit'
+require 'active_support/xml_mini'
+require 'active_support/core_ext/hash/conversions'
class LibXMLSAXEngineTest < ActiveSupport::TestCase
include ActiveSupport
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index 3f37c7cbb6..71f57e43d2 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -1,12 +1,11 @@
-require 'abstract_unit'
-require 'active_support/xml_mini'
-require 'active_support/core_ext/hash/conversions'
-
begin
require 'nokogiri'
rescue LoadError
# Skip nokogiri tests
else
+require 'abstract_unit'
+require 'active_support/xml_mini'
+require 'active_support/core_ext/hash/conversions'
class NokogiriEngineTest < ActiveSupport::TestCase
include ActiveSupport
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index d6ae7f12ae..884494e95e 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -1,12 +1,11 @@
-require 'abstract_unit'
-require 'active_support/xml_mini'
-require 'active_support/core_ext/hash/conversions'
-
begin
require 'nokogiri'
rescue LoadError
# Skip nokogiri tests
else
+require 'abstract_unit'
+require 'active_support/xml_mini'
+require 'active_support/core_ext/hash/conversions'
class NokogiriSAXEngineTest < ActiveSupport::TestCase
include ActiveSupport
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index 11fe1c5efa..e9f7ff9d68 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Split Validations and Callbacks guide into two. *Steve Klabnik*
+
* New guide _Working with JavaScript in Rails_. *Steve Klabnik*
* Guides updated to reflect new test locations. *Mike Moore*
diff --git a/guides/Rakefile b/guides/Rakefile
index 7881a3d9b3..d6dd950d01 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -13,6 +13,12 @@ namespace :guides do
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
+ unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/
+ abort "Please `gem install kindlerb`"
+ end
+ unless `convert` =~ /convert/
+ abort "Please install ImageMagick`"
+ end
ENV['KINDLE'] = '1'
Rake::Task['guides:generate:html'].invoke
end
diff --git a/guides/assets/images/customized_error_messages.png b/guides/assets/images/customized_error_messages.png
deleted file mode 100644
index fcf47b4be0..0000000000
--- a/guides/assets/images/customized_error_messages.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/error_messages.png b/guides/assets/images/error_messages.png
deleted file mode 100644
index 1189e486d4..0000000000
--- a/guides/assets/images/error_messages.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/images/rails4_features.png b/guides/assets/images/rails4_features.png
new file mode 100644
index 0000000000..a979f02207
--- /dev/null
+++ b/guides/assets/images/rails4_features.png
Binary files differ
diff --git a/guides/assets/images/validation_error_messages.png b/guides/assets/images/validation_error_messages.png
deleted file mode 100644
index 30e4ca4a3d..0000000000
--- a/guides/assets/images/validation_error_messages.png
+++ /dev/null
Binary files differ
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index 589c96e0e9..dd029e6314 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -83,6 +83,10 @@ table th {
padding: 0.5em 1em;
}
+img {
+ max-width: 100%;
+}
+
/* Structure and Layout
--------------------------------------- */
@@ -573,7 +577,7 @@ h6 {
#mainCol div.warning, #subCol dd.warning {
background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top;
border: none;
- padding: 1.25em 1.25em 1.25em 48px;
+ padding: 1.25em 1.25em 0.25em 48px;
margin-left: 0;
margin-top: 0.25em;
}
diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb
index a8ac9aba5a..b74c66ef13 100644
--- a/guides/code/getting_started/app/controllers/posts_controller.rb
+++ b/guides/code/getting_started/app/controllers/posts_controller.rb
@@ -31,7 +31,7 @@ class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
- if @post.update_attributes(params[:post])
+ if @post.update(params[:post])
redirect_to :action => :show, :id => @post.id
else
render 'edit'
diff --git a/guides/code/getting_started/config/application.rb b/guides/code/getting_started/config/application.rb
index d2cd5c028b..d53c9fd8bc 100644
--- a/guides/code/getting_started/config/application.rb
+++ b/guides/code/getting_started/config/application.rb
@@ -18,9 +18,6 @@ module Blog
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
- # Activate observers that should always be running.
- # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
-
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index 3b124ef236..af9c5b8372 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -84,7 +84,7 @@ module RailsGuides
@warnings = ENV['WARNINGS'] == '1'
@all = ENV['ALL'] == '1'
@kindle = ENV['KINDLE'] == '1'
- @version = ENV['RAILS_VERSION'] || `git rev-parse --short HEAD`.chomp
+ @version = ENV['RAILS_VERSION'] || 'local'
@lang = ENV['GUIDES_LANGUAGE']
end
@@ -112,11 +112,9 @@ module RailsGuides
end
def generate_mobi
- opf = "#{output_dir}/rails_guides.opf"
+ require 'rails_guides/kindle'
out = "#{output_dir}/kindlegen.out"
-
- system "kindlegen #{opf} -o #{mobi} > #{out} 2>&1"
- puts "Guides compiled as Kindle book to #{mobi}"
+ Kindle.generate(output_dir, mobi, out)
puts "(kindlegen log at #{out})."
end
diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb
new file mode 100644
index 0000000000..09eecd5634
--- /dev/null
+++ b/guides/rails_guides/kindle.rb
@@ -0,0 +1,119 @@
+#!/usr/bin/env ruby
+
+unless `which kindlerb`
+ abort "Please gem install kindlerb"
+end
+
+require 'nokogiri'
+require 'fileutils'
+require 'yaml'
+require 'date'
+
+module Kindle
+ extend self
+
+ def generate(output_dir, mobi_outfile, logfile)
+ output_dir = File.absolute_path(output_dir)
+ Dir.chdir output_dir do
+ puts "=> Using output dir: #{output_dir}"
+ puts "=> Arranging html pages in document order"
+ toc = File.read("toc.ncx")
+ doc = Nokogiri::XML(toc).xpath("//ncx:content", 'ncx' => "http://www.daisy.org/z3986/2005/ncx/")
+ html_pages = doc.select {|c| c[:src]}.map {|c| c[:src]}.uniq
+
+ generate_front_matter(html_pages)
+
+ generate_sections(html_pages)
+
+ generate_document_metadata(mobi_outfile)
+
+ puts "Creating MOBI document with kindlegen. This make take a while."
+ cmd = "kindlerb . > #{File.absolute_path logfile} 2>&1"
+ puts cmd
+ system(cmd)
+ puts "MOBI document generated at #{File.expand_path(mobi_outfile, output_dir)}"
+ end
+ end
+
+ def generate_front_matter(html_pages)
+ frontmatter = []
+ html_pages.delete_if {|x|
+ if x =~ /(toc|welcome|credits|copyright).html/
+ frontmatter << x unless x =~ /toc/
+ true
+ end
+ }
+ html = frontmatter.map {|x|
+ Nokogiri::HTML(File.open(x)).at("body").inner_html
+ }.join("\n")
+
+ fdoc = Nokogiri::HTML(html)
+ fdoc.search("h3").each do |h3|
+ h3.name = 'h4'
+ end
+ fdoc.search("h2").each do |h2|
+ h2.name = 'h3'
+ h2['id'] = h2.inner_text.gsub(/\s/, '-')
+ end
+ add_head_section fdoc, "Front Matter"
+ File.open("frontmatter.html",'w') {|f| f.puts fdoc.to_html}
+ html_pages.unshift "frontmatter.html"
+ end
+
+ def generate_sections(html_pages)
+ FileUtils::rm_rf("sections/")
+ html_pages.each_with_index do |page, section_idx|
+ FileUtils::mkdir_p("sections/%03d" % section_idx)
+ doc = Nokogiri::HTML(File.open(page))
+ title = doc.at("title").inner_text.gsub("Ruby on Rails Guides: ", '')
+ title = page.capitalize.gsub('.html', '') if title.strip == ''
+ File.open("sections/%03d/_section.txt" % section_idx, 'w') {|f| f.puts title}
+ doc.xpath("//h3[@id]").each_with_index do |h3,item_idx|
+ subsection = h3.inner_text
+ content = h3.xpath("./following-sibling::*").take_while {|x| x.name != "h3"}.map {|x| x.to_html}
+ item = Nokogiri::HTML(h3.to_html + content.join("\n"))
+ item_path = "sections/%03d/%03d.html" % [section_idx, item_idx]
+ add_head_section(item, subsection)
+ item.search("img").each do |img|
+ img['src'] = "#{Dir.pwd}/#{img['src']}"
+ end
+ item.xpath("//li/p").each {|p| p.swap(p.children); p.remove}
+ File.open(item_path, 'w') {|f| f.puts item.to_html}
+ end
+ end
+ end
+
+ def generate_document_metadata(mobi_outfile)
+ puts "=> Generating _document.yml"
+ x = Nokogiri::XML(File.open("rails_guides.opf")).remove_namespaces!
+ cover_jpg = "#{Dir.pwd}/images/rails_guides_kindle_cover.jpg"
+ cover_gif = cover_jpg.sub(/jpg$/, 'gif')
+ puts `convert #{cover_jpg} #{cover_gif}`
+ document = {
+ 'doc_uuid' => x.at("package")['unique-identifier'],
+ 'title' => x.at("title").inner_text.gsub(/\(.*$/, " v2"),
+ 'publisher' => x.at("publisher").inner_text,
+ 'author' => x.at("creator").inner_text,
+ 'subject' => x.at("subject").inner_text,
+ 'date' => x.at("date").inner_text,
+ 'cover' => cover_gif,
+ 'masthead' => nil,
+ 'mobi_outfile' => mobi_outfile
+ }
+ puts document.to_yaml
+ File.open("_document.yml", 'w'){|f| f.puts document.to_yaml}
+ end
+
+ def add_head_section(doc, title)
+ head = Nokogiri::XML::Node.new "head", doc
+ title_node = Nokogiri::XML::Node.new "title", doc
+ title_node.content = title
+ title_node.parent = head
+ css = Nokogiri::XML::Node.new "link", doc
+ css['rel'] = 'stylesheet'
+ css['type'] = 'text/css'
+ css['href'] = "#{Dir.pwd}/stylesheets/kindle.css"
+ css.parent = head
+ doc.at("body").before head
+ end
+end
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index ecb8dd04f5..80af0c1225 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -1,41 +1,23 @@
Ruby on Rails 4.0 Release Notes
===============================
-Highlights in Rails 4.0: (WIP)
+Highlights in Rails 4.0:
* Ruby 1.9.3 only
* Strong Parameters
-* Queue API
-* Caching Improvements
+* Turbolinks
+* Russian Doll Caching
+* Asynchronous Mailers
-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.
+These release notes cover only the major changes. To know about various bug fixes and changes, please refer to the change logs or check out the [list of commits](https://github.com/rails/rails/commits/master) in the main Rails repository on GitHub.
--------------------------------------------------------------------------------
Upgrading to Rails 4.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. A list of things to watch out for when upgrading is available in the [Upgrading to Rails](upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0) 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:
-
-### 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.
-
-### What to update in your apps
-
-* Update your Gemfile to depend on
- * `rails = 4.0.0`
- * `sass-rails ~> 3.2.3`
- * `coffee-rails ~> 3.2.1`
- * `uglifier >= 1.0.3`
-
-TODO: Update the versions above.
-
-* Rails 4.0 removes `vendor/plugins` 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, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
-
-TODO: Configuration changes in environment files
Creating a Rails 4.0 application
--------------------------------
@@ -69,6 +51,24 @@ $ ruby /path/to/rails/railties/bin/rails new myapp --dev
Major Features
--------------
+TODO. Give a list and then talk about each of them briefly. We can point to relevant code commits or documentation from these sections.
+
+![Rails 4.0](images/rails4_features.png)
+
+Extraction of features to gems
+---------------------------
+
+In Rails 4.0, several features have been extracted into gems. You can simply add the extracted gems to your `Gemfile` to bring the functionality back.
+
+* Hash-based & Dynamic finder methods ([Github](https://github.com/rails/activerecord-deprecated_finders))
+* Mass assignment protection in Active Record models ([Github](https://github.com/rails/protected_attributes), [Pull Request](https://github.com/rails/rails/pull/7251))
+* ActiveRecord::SessionStore ([Github](https://github.com/rails/activerecord-session_store), [Pull Request](https://github.com/rails/rails/pull/7436))
+* Active Record Observers ([Github](https://github.com/rails/rails-observers), [Commit](https://github.com/rails/rails/commit/39e85b3b90c58449164673909a6f1893cba290b2))
+* Active Resource ([Github](https://github.com/rails/activeresource), [Pull Request](https://github.com/rails/rails/pull/572), [Blog](http://yetimedia.tumblr.com/post/35233051627/activeresource-is-dead-long-live-activeresource))
+* Action Caching ([Github](https://github.com/rails/actionpack-action_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
+* Page Caching ([Github](https://github.com/rails/actionpack-page_caching), [Pull Request](https://github.com/rails/rails/pull/7833))
+* Sprockets ([Github](https://github.com/rails/sprockets-rails))
+
Documentation
-------------
@@ -79,841 +79,144 @@ Documentation
Railties
--------
-* Ensure that RAILS_ENV is set when accessing Rails.env.
-
-* Don't eager-load app/assets and app/views.
-
-* Add `.rake` to list of file extensions included by `rake notes` and `rake notes:custom`.
-
-* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well.
-
-* Set a different cache per environment for assets pipeline through `config.assets.cache`.
-
-* `Rails.public_path` now returns a Pathname object.
-
-* Remove highly uncommon `config.assets.manifest` option for moving the manifest path. This option is now unsupported in sprockets-rails.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes.
-* Add `config.action_controller.permit_all_parameters` to disable StrongParameters protection, it's false by default.
+### Notable changes
-* Remove `config.active_record.whitelist_attributes` and `config.active_record.mass_assignment_sanitizer` from new applications since MassAssignmentSecurity has been extracted from Rails.
+* New test locations `test/models`, `test/helpers`, `test/controllers`, and `test/mailers`. Corresponding rake tasks added as well. ([Pull Request](https://github.com/rails/rails/pull/7878))
-* Change `rails new` and `rails plugin new` generators to name the `.gitkeep` files as `.keep` in a more SCM-agnostic way. Change `--skip-git` option to only skip the `.gitignore` file and still generate the `.keep` files. Add `--skip-keeps` option to skip the `.keep` files.
+* Threadsafe on by default
-* Fixed support for DATABASE_URL environment variable for rake db tasks.
-
-* rails dbconsole now can use SSL for MySQL. The database.yml options sslca, sslcert, sslcapath, sslcipher and sslkey now affect rails dbconsole.
-
-* Correctly handle SCRIPT_NAME when generating routes to engine in application that's mounted at a sub-uri. With this behavior, you *should not* use default_url_options[:script_name] to set proper application's mount point by yourself.
-
-* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded.
-
-* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance
+### Deprecations
- rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
+* `config.threadsafe!` is deprecated in favor of `config.eager_load` which provides a more fine grained control on what is eager loaded.
- will generate the migration with:
+* `Rails::Plugin` has gone. Instead of adding plugins to `vendor/plugins` use gems or bundler with path or git dependencies.
- add_reference :products, :user, index: true
- add_reference :products, :supplier, polymorphic: true, index: true
+Action Mailer
+-------------
-* Allow scaffold/model/migration generators to accept a `polymorphic` modifier for `references`/`belongs_to`, for instance
+Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionmailer/CHANGELOG.md) for detailed changes.
- ```
- rails g model Product supplier:references{polymorphic}
- ```
+### Notable changes
- will generate the model with `belongs_to :supplier, polymorphic: true` association and appropriate migration.
+### Deprecations
-* Set `config.active_record.migration_error` to `:page_load` for development.
+Active Model
+------------
-* Add runner to `Rails::Railtie` as a hook called just after runner starts.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activemodel/CHANGELOG.md) for detailed changes.
-* Add `/rails/info/routes` path which displays the same information as `rake routes`.
+### Notable changes
-* Improved `rake routes` output for redirects.
+* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed.
-* Load all environments available in `config.paths["config/environments"]`.
+* Added `ActiveModel::Model`, a mixin to make Ruby objects work with AP out of box.
-* Add `config.queue_consumer` to change the job queue consumer from the default `ActiveSupport::ThreadedQueueConsumer`.
+### Deprecations
-* Add `Rails.queue` for processing jobs in the background.
+Active Support
+--------------
-* Remove `Rack::SSL` in favour of `ActionDispatch::SSL`.
+Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activesupport/CHANGELOG.md) for detailed changes.
-* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to console block.
+### Notable changes
- ```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
- ```
+* Replace deprecated `memcache-client` gem with `dalli` in ActiveSupport::Cache::MemCacheStore.
-* Add a convenience method `hide!` to Rails generators to hide the current generator namespace from showing when running `rails generate`.
+* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead.
-* Scaffold now uses `content_tag_for` in `index.html.erb`.
+* Inflections can now be defined per locale. `singularize` and `pluralize` accept locale as an extra argument.
-* `Rails::Plugin` is removed. Instead of adding plugins to `vendor/plugins`, use gems or bundler with path or git dependencies.
+* `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`.
### Deprecations
-Action Mailer
--------------
-
-* Allow to set default Action Mailer options via `config.action_mailer.default_options=`.
-
-* Raise an `ActionView::MissingTemplate` exception when no implicit template could be found.
+* Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead.
-* Asynchronously send messages via the Rails Queue.
+* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1.
-* Delivery Options (such as SMTP Settings) can now be set dynamically per mailer action.
+* `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals.
- Delivery options are set via <tt>:delivery_method_options</tt> key on mail.
+* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols).
- ```ruby
- def welcome_mailer(user,company)
- delivery_options = { user_name: company.smtp_user, password: company.smtp_password, address: company.smtp_host }
- mail(to: user.email, subject: "Welcome!", delivery_method_options: delivery_options)
- end
- ```
+* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby stdlib.
-* Allow for callbacks in mailers similar to ActionController::Base. You can now set up headers/attachments using `before_filter` or `after_filter`. You could also change delivery settings or prevent delivery in an after filter based on instance variables set in your mailer action. You have access to `ActionMailer::Base` instance methods like `message`, `attachments`, `headers`.
+* Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?`
Action Pack
-----------
-### Action Controller
-
-* Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.:
-
- ```ruby
- class ApplicationController
- add_flash_types :error, :warning
- end
- ```
-
- If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, :error => 'message'` in a controller.
-
-* Remove Active Model dependency from Action Pack.
-
-* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping:
-
- ```ruby
- get Rack::Utils.escape('こんにちは') => 'home#index'
- ```
-
- You just have to write the unicode route:
-
- ```ruby
- get 'こんにちは' => 'home#index'
- ```
-
-* Return proper format on exceptions.
-
-* Extracted redirect logic from `ActionController::ForceSSL::ClassMethods.force_ssl` into `ActionController::ForceSSL#force_ssl_redirect`.
-
-* URL path parameters with invalid encoding now raise `ActionController::BadRequest`.
-
-* Malformed query and request parameter hashes now raise `ActionController::BadRequest`.
-
-* `respond_to` and `respond_with` now raise `ActionController::UnknownFormat` instead of directly returning head 406. The exception is rescued and converted to 406 in the exception handling middleware.
-
-* JSONP now uses `application/javascript` instead of `application/json` as the MIME type.
-
-* 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 an `:if` condition to recreate the old behavior.
-
- ```ruby
- class AccountsController < ApplicationController
- force_ssl :if => :ssl_configured?
-
- def ssl_configured?
- !Rails.env.development?
- end
- end
- ```
-
-#### 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`.
-
-### Action Dispatch
-
-* Add Routing Concerns to declare common routes that can be reused inside others resources and routes.
-
- Code before:
-
- ```ruby
- resources :messages do
- resources :comments
- end
-
- resources :posts do
- resources :comments
- resources :images, only: :index
- end
- ```
+Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes.
- Code after:
+### Notable changes
- ```ruby
- concern :commentable do
- resources :comments
- end
+* Change the stylesheet of exception pages for development mode. Additionally display also the line of code and fragment that raised the exception in all exceptions pages.
- concern :image_attachable do
- resources :images, only: :index
- end
-
- resources :messages, concerns: :commentable
-
- resources :posts, concerns: [:commentable, :image_attachable]
- ```
-
-* Show routes in exception page while debugging a `RoutingError` in development.
-
-* Include `mounted_helpers` (helpers for accessing mounted engines) in `ActionDispatch::IntegrationTest` by default.
-
-* Added `ActionDispatch::SSL` 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.
-
-* Adds a backtrace to the routing error page in development.
-
-* `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, `root 'pages#main'` as a shortcut for `root to: 'pages#main'`.
-
-* Adds support for the PATCH verb: Request objects respond to `patch?`. Routes now have a new `patch` method, and understand `:patch` in the existing places where a verb is configured, like `:via`. Functional tests have a new method `patch` and integration tests have a new method `patch_via_redirect`.
-If `:patch` is the default verb for updates, edits are tunneled as `PATCH` rather than as `PUT` and routing acts accordingly.
-
-* 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.
-
-#### Deprecations
-
-### Action View
-
-* Remove Active Model dependency from Action Pack.
-
-* Allow to use `mounted_helpers` (helpers for accessing mounted engines) in `ActionView::TestCase`.
-
-* Make current object and counter (when it applies) variables accessible when rendering templates with `:object` or `: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 `:disable_with` in favor of `data-disable-with` option from `submit_tag`, `button_tag` and `button_to` helpers.
-
-* Remove `:mouseover` 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 a `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 `input[type="time"]` tag.
-
-* Removed old `text_helper` apis for `highlight`, `excerpt` and `word_wrap`.
-
-* Remove the leading \n added by textarea on `assert_select`.
-
-* Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms` 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 `:authenticity_token => true` 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>"
- ```
-
-* Replace `include_seconds` boolean argument with `:include_seconds => true` 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 `:include_hidden => false` 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 `input[type="date"]` 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="" />
- ```
-
- 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>
- ```
-
- The label/radio_button pairs can be customized with a block.
-
-* `check_box` with an HTML5 attribute `:form` 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 `config.action_view.logger` 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 `app/assets` by default.
-
-* `ActionView::Helpers::TextHelper#highlight` now defaults to the HTML5 `mark` element.
-
-#### Deprecations
-
-### Sprockets
+### Deprecations
-Moved into a separate gem `sprockets-rails`.
Active Record
-------------
-* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to` and `remove_belongs_to` are acceptable. References are reversible.
-
- ```ruby
- # Create a user_id column
- add_reference(:products, :user)
-
- # Create a supplier_id, supplier_type columns and appropriate index
- add_reference(:products, :supplier, polymorphic: true, index: true)
-
- # Remove polymorphic reference
- remove_reference(:products, :supplier, polymorphic: true)
- ```
-
-* Add `:default` and `:null` options to `column_exists?`.
-
- ```ruby
- column_exists?(:testings, :taggable_id, :integer, null: false)
- column_exists?(:testings, :taggable_type, :string, default: 'Photo')
- ```
-
-* `ActiveRecord::Relation#inspect` now makes it clear that you are dealing with a `Relation` object rather than an array:
-
- ```ruby
- User.where(:age => 30).inspect
- # => <ActiveRecord::Relation [#<User ...>, #<User ...>]>
-
- User.where(:age => 30).to_a.inspect
- # => [#<User ...>, #<User ...>]
- ```
+Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes.
- if more than 10 items are returned by the relation, inspect will only show the first 10 followed by ellipsis.
+### Notable changes
-* Add `:collation` and `:ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later.
+* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary.
- ```yaml
- development:
- adapter: postgresql
- host: localhost
- database: rails_development
- username: foo
- password: bar
- encoding: UTF8
- collation: ja_JP.UTF8
- ctype: ja_JP.UTF8
- ```
+ * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given.
+ The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible).
+ The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default`
-* `FinderMethods#exists?` now returns `false` with the `false` argument.
+ * New method `reversible` makes it possible to specify code to be run when migrating up or down.
+ See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method)
-* Added support for specifying the precision of a timestamp in the postgresql adapter. So, instead of having to incorrectly specify the precision using the `:limit` option, you may use `:precision`, as intended. For example, in a migration:
+ * New method `revert` will revert a whole migration or the given block.
+ If migrating down, the given migration / block is run normally.
+ See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations)
- ```ruby
- def change
- create_table :foobars do |t|
- t.timestamps :precision => 0
- end
- end
- ```
+* Adds some metadata columns to `schema_migrations` table.
-* Allow `ActiveRecord::Relation#pluck` to accept multiple columns. Returns an array of arrays containing the typecasted values:
+ * `migrated_at`
+ * `fingerprint` - an md5 hash of the migration.
+ * `name` - the filename minus version and extension.
- ```ruby
- Person.pluck(:id, :name)
- # SELECT people.id, people.name FROM people
- # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
- ```
+* Adds PostgreSQL array type support. Any datatype can be used to create an array column, with full migration and schema dumper support.
-* Improve the derivation of HABTM join table name to take account of nesting. It now takes the table names of the two models, sorts them lexically and then joins them, stripping any common prefix from the second table name. Some examples:
+* Add `Relation#load` to explicitly load the record and return `self`.
- ```
- Top level models (Category <=> Product)
- Old: categories_products
- New: categories_products
-
- Top level models with a global table_name_prefix (Category <=> Product)
- Old: site_categories_products
- New: site_categories_products
-
- Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product)
- Old: categories_products
- New: categories_products
-
- Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product)
- Old: categories_products
- New: admin_categories_products
-
- Nested models in a parent model (Catalog::Category <=> Catalog::Product)
- Old: categories_products
- New: catalog_categories_products
-
- Nested models in different parent models (Catalog::Category <=> Content::Page)
- Old: categories_pages
- New: catalog_categories_content_pages
- ```
-
-* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of this is to move when the exceptions are raised from the point of declaration to when the association is built. This is consistant with other association validity checks.
-
-* Added `stored_attributes` hash which contains the attributes stored using `ActiveRecord::Store`. This allows you to retrieve the list of attributes you've defined.
-
- ```ruby
- class User < ActiveRecord::Base
- store :settings, accessors: [:color, :homepage]
- end
-
- User.stored_attributes[:settings] # [:color, :homepage]
- ```
-
-* 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`.
-
-* Add uuid datatype support to PostgreSQL adapter.
+* `Model.all` now returns an `ActiveRecord::Relation`, rather than an array of records. Use `Relation#to_a` if you really want an array. In some specific cases, this may cause breakage when upgrading.
* Added `ActiveRecord::Migration.check_pending!` that raises an error if migrations are pending.
-* Added `#destroy!` which acts like `#destroy` but will raise an `ActiveRecord::RecordNotDestroyed` exception instead of returning `false`.
-
-* Allow blocks for count with `ActiveRecord::Relation`, to work similar as `Array#count`: `Person.where("age > 26").count { |person| person.gender == 'female' }`
-
-* Added support to `CollectionAssociation#delete` for passing fixnum or string values as record ids. This finds the records responding to the ids and deletes 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>]
- ```
-
-* It's not possible anymore to destroy a model marked as read only.
-
-* Added ability to `ActiveRecord::Relation#from` to accept other `ActiveRecord::Relation` objects.
-
* Added custom coders support for `ActiveRecord::Store`. Now you can set your custom coder like this:
- ```ruby
- store :settings, accessors: [ :color, :homepage ], coder: JSON
- ```
-
-* `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 `config/database.yml`.
-
-* Added default order to `ActiveRecord::Base#first` to assure consistent results among different database engines. Introduced `ActiveRecord::Base#take` as a replacement to the old behavior.
-
-* Added an `:index` option to automatically create indexes for `references` and `belongs_to` statements in migrations. This can be either a boolean or a hash that is identical to options available to the `add_index` method:
-
- ```ruby
- create_table :messages do |t|
- t.references :person, :index => true
- end
- ```
-
- Is the same as:
-
- ```ruby
- create_table :messages do |t|
- t.references :person
- end
- add_index :messages, :person_id
- ```
-
- Generators have also been updated to use the new syntax.
-
-* Added bang methods for mutating `ActiveRecord::Relation` objects. For example, while `foo.where(:bar)` will return a new object leaving foo unchanged, `foo.where!(:bar)` 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'
- ```
-
-* Added `ActiveRecord::Base#slice` 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 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
- ```
+ store :settings, accessors: [ :color, :homepage ], coder: JSON
-* Added support for partial indices to `PostgreSQL` adapter.
+* `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`.
-* The `add_index` method now supports a `where` option that receives a string with the partial index criteria.
+* Remove IdentityMap.
-* Added the `ActiveRecord::NullRelation` class implementing the null object pattern for the Relation class.
-
-* Implemented `ActiveRecord::Relation#none` 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.
+* Adds `ActiveRecord::NullRelation` and `ActiveRecord::Relation#none` 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
- ```
-
-* 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'")
- ```
-
- 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)
- ```
-
- 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')
- ```
-
- 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.
-
-* PostgreSQL hstore records can be created.
-
-* PostgreSQL hstore types are automatically deserialized from the database.
-
-* Added `#update_columns` method which updates the attributes from the passed-in hash without calling save, hence skipping validations and callbacks. `ActiveRecordError` will be raised when called on new objects or when at least one of the attributes is marked as read only.
-
- ```ruby
- post.attributes # => {"id"=>2, "title"=>"My title", "body"=>"My content", "author"=>"Peter"}
- post.update_columns({title: 'New title', author: 'Sebastian'}) # => true
- post.attributes # => {"id"=>2, "title"=>"New title", "body"=>"My content", "author"=>"Sebastian"}
- ```
-
-### 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!
- ```
-
- 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)
- ```
-
- should be rewritten in the new style which has existed since Rails 3:
-
- ```ruby
- Post.where(comments_count: 10).limit(5)
- ```
-
- Note that as an interim step, it is possible to rewrite the above as:
-
- ```ruby
- Post.scoped(:where => { :comments_count => 10 }, :limit => 5)
- ```
-
- This could save you a lot of work if there is a lot of old-style finder usage in your application.
-
- Calling `Post.scoped(options)` is a shortcut for `Post.scoped.merge(options)`. `Relation#merge` 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:
-
- ```
- :conditions becomes :where
- :include becomes :includes
- :extend becomes :extending
- ```
-
- 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:
-
- ```ruby
- scope :red, where(color: 'red')
- default_scope where(color: 'red')
- ```
-
- Use this:
-
- ```ruby
- 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:
-
- ```ruby
- scope :recent, where(published_at: Time.now - 2.weeks)
- ```
-
- Or a more subtle variant:
-
- ```ruby
- 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:
-
- ```ruby
- 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.
-
-* The migration generator now creates a join table with (commented) indexes every time the migration name contains the word "join_table".
-
-* `ActiveRecord::SessionStore` is removed from Rails 4.0 and is now a separate [gem](https://github.com/rails/activerecord-session_store).
-
-Active Model
-------------
-
-* Changed `AM::Serializers::JSON.include_root_in_json` default value to false. Now, AM Serializers and AR objects have the same default behaviour.
-
- ```ruby
- 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
- ```
-
-* Passing false hash values to `validates` will no longer enable the corresponding validators.
-
-* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute`.
-
-* Added `ActiveModel::Model`, a mixin to make Ruby objects work with Action Pack out of the box.
-
-* `ActiveModel::Errors#to_json` supports a new parameter `:full_messages`.
-
-* Trims down the API by removing `valid?` and `errors.full_messages`.
-
-### Deprecations
-
-Active Resource
----------------
-
-* Active Resource is removed from Rails 4.0 and is now a separate [gem](https://github.com/rails/activeresource).
-
-Active Support
---------------
-
-* Add default values to all `ActiveSupport::NumberHelper` methods, to avoid errors with empty locales or missing values.
-
-* `Time#change` now works with time values with offsets other than UTC or the local time zone.
-
-* Add `Time#prev_quarter` and `Time#next_quarter` short-hands for `months_ago(3)` and `months_since(3)`.
-
-* Add `Time#last_week`, `Time#last_month`, `Time#last_year` as aliases for `Time#prev_week`, `Time#prev_month`, and `Time#prev_year`.
-
-* Add `Date#last_week`, `Date#last_month`, `Date#last_year` as aliases for `Date#prev_week`, `Date#prev_month`, and `Date#prev_year`.
-
-* Remove obsolete and unused `require_association` method from dependencies.
-
-* Add `:instance_accessor` option for `config_accessor`.
-
- ```ruby
- class User
- include ActiveSupport::Configurable
- config_accessor :allowed_access, instance_accessor: false
- end
-
- User.new.allowed_access = true # => NoMethodError
- User.new.allowed_access # => NoMethodError
- ```
-
-* `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.
-
-* Add `Hash#transform_keys`, `Hash#transform_keys!`, `Hash#deep_transform_keys` and `Hash#deep_transform_keys!`.
-
-* Changed xml type datetime to dateTime (with upper case letter T).
-
-* Add `:instance_accessor` option for `class_attribute`.
-
-* `constantize` now looks in the ancestor chain.
-
-* Add `Hash#deep_stringify_keys` and `Hash#deep_stringify_keys!` to convert all keys from a `Hash` instance into strings.
-
-* Add `Hash#deep_symbolize_keys` and `Hash#deep_symbolize_keys!` to convert all keys from a `Hash` instance into symbols.
-
-* `Object#try` 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 `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations.
-
-* Make `Module#delegate` stop using send - can no longer delegate to private methods.
-
-* AS::Callbacks deprecate :rescuable option.
-
-* Adds `Integer#ordinal` 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 `ActiveSupport::TestCase#pending` method, use `skip` instead.
-
-* Deletes the compatibility method `Module#method_names`, use `Module#methods` from now on (which returns symbols).
-
-* Deletes the compatibility method `Module#instance_method_names`, use `Module#instance_methods` 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.
+* Allows PostgreSQL hstore records to be created.
### Deprecations
-* `ActiveSupport::Callbacks`: deprecate usage of filter object with `#before` and `#after` methods as `around` callback.
+* Deprecated the old-style hash based finder API. This means that methods which previously accepted "finder options" no longer do.
-* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger` or the `logger` from Ruby stdlib.
+* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. Here's
+ how you can rewrite the code:
-* Deprecates the compatibility method `Module#local_constant_names` and use `Module#local_constants` instead (which returns symbols).
+ * `find_all_by_...` can be rewritten using `where(...)`.
+ * `find_last_by_...` can be rewritten using `where(...).last`.
+ * `scoped_by_...` can be rewritten using `where(...)`.
+ * `find_or_initialize_by_...` can be rewritten using `where(...).first_or_initialize`.
+ * `find_or_create_by_...` can be rewritten using `find_or_create_by(...)` or `where(...).first_or_create`.
+ * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)` or `where(...).first_or_create!`.
Credits
-------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 9d2e9c1d68..a50961a0c7 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -1,4 +1,4 @@
-<h2>Ruby on Rails Guides (<%= @version %>)</h2>
+<h2>Ruby on Rails Guides (<%= @edge ? @version[0, 7] : @version %>)</h2>
<% if @edge %>
<p>
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 6f161e83ea..46ff9027fd 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1,15 +1,17 @@
Action Controller Overview
==========================
-In this guide you will learn how controllers work and how they fit into the request cycle in your application. After reading this guide, you will be able to:
+In this guide you will learn how controllers work and how they fit into the request cycle in your application.
-* Follow the flow of a request through a controller
-* Understand why and how to store data in the session or cookies
-* Work with filters to execute code during request processing
-* Use Action Controller's built-in HTTP authentication
-* Stream data directly to the user's browser
-* Filter sensitive parameters so they do not appear in the application's log
-* Deal with exceptions that may be raised during request processing
+After reading this guide, you will know:
+
+* How to follow the flow of a request through a controller.
+* Why and how to store data in the session or cookies.
+* How to work with filters to execute code during request processing.
+* How to use Action Controller's built-in HTTP authentication.
+* How to stream data directly to the user's browser.
+* How to filter sensitive parameters so they do not appear in the application's log.
+* How to deal with exceptions that may be raised during request processing.
--------------------------------------------------------------------------------
@@ -432,7 +434,7 @@ Filters are inherited, so if you set a filter on `ApplicationController`, it wil
```ruby
class ApplicationController < ActionController::Base
- before_filter :require_login
+ before_action :require_login
private
@@ -456,11 +458,11 @@ end
The method simply stores an error message in the flash and redirects to the login form if the user is not logged in. If a "before" filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter, they are also cancelled.
-In this example the filter is added to `ApplicationController` and thus all controllers in the application inherit it. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this. You can prevent this filter from running before particular actions with `skip_before_filter`:
+In this example the filter is added to `ApplicationController` and thus all controllers in the application inherit it. This will make everything in the application require the user to be logged in in order to use it. For obvious reasons (the user wouldn't be able to log in in the first place!), not all controllers or actions should require this. You can prevent this filter from running before particular actions with `skip_before_action`:
```ruby
class LoginsController < ApplicationController
- skip_before_filter :require_login, only: [:new, :create]
+ skip_before_action :require_login, only: [:new, :create]
end
```
@@ -478,7 +480,7 @@ For example, in a website where changes have an approval workflow an administrat
```ruby
class ChangesController < ActionController::Base
- around_filter :wrap_in_transaction, only: :show
+ around_action :wrap_in_transaction, only: :show
private
@@ -500,13 +502,13 @@ You can choose not to yield and build the response yourself, in which case the a
### Other Ways to Use Filters
-While the most common way to use filters is by creating private methods and using *_filter to add them, there are two other ways to do the same thing.
+While the most common way to use filters is by creating private methods and using *_action to add them, there are two other ways to do the same thing.
-The first is to use a block directly with the *_filter methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block:
+The first is to use a block directly with the *_action methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block:
```ruby
class ApplicationController < ActionController::Base
- before_filter do |controller|
+ before_action do |controller|
redirect_to new_login_url unless controller.send(:logged_in?)
end
end
@@ -518,7 +520,7 @@ The second way is to use a class (actually, any object that responds to the righ
```ruby
class ApplicationController < ActionController::Base
- before_filter LoginFilter
+ before_action LoginFilter
end
class LoginFilter
@@ -646,7 +648,7 @@ HTTP digest authentication is superior to the basic authentication as it does no
class AdminController < ApplicationController
USERS = { "lifo" => "world" }
- before_filter :authenticate
+ before_action :authenticate
private
@@ -749,15 +751,36 @@ Now the user can request to get a PDF version of a client just by adding ".pdf"
GET /clients/1.pdf
```
-Parameter Filtering
--------------------
+Log Filtering
+-------------
+
+Rails keeps a log file for each environment in the `log` folder. These are extremely useful when debugging what's actually going on in your application, but in a live application you may not want every bit of information to be stored in the log file.
+
+### Parameters Filtering
-Rails keeps a log file for each environment in the `log` folder. These are extremely useful when debugging what's actually going on in your application, but in a live application you may not want every bit of information to be stored in the log file. You can filter certain request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log.
+You can filter certain request parameters from your log files by appending them to `config.filter_parameters` in the application configuration. These parameters will be marked [FILTERED] in the log.
```ruby
config.filter_parameters << :password
```
+### Redirects Filtering
+
+Sometimes it's desirable to filter out from log files some sensible locations your application is redirecting to.
+You can do that by using the `config.filter_redirect` configuration option:
+
+```ruby
+config.filter_redirect << 's3.amazonaws.com'
+```
+
+You can set it to a String, a Regexp, or an array of both.
+
+```ruby
+config.filter_redirect.concat ['s3.amazonaws.com', /private_path/]
+```
+
+Matching URLs will be marked as '[FILTERED]'.
+
Rescue
------
@@ -805,7 +828,7 @@ end
class ClientsController < ApplicationController
# Check that the user has the right authorization to access clients.
- before_filter :check_authorization
+ before_action :check_authorization
# Note how the actions don't have to worry about all the auth stuff.
def edit
@@ -826,7 +849,7 @@ NOTE: Certain exceptions are only rescuable from the `ApplicationController` cla
Force HTTPS protocol
--------------------
-Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reasons. Since Rails 3.1 you can now use the `force_ssl` method in your controller to enforce that:
+Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reasons. You can use the `force_ssl` method in your controller to enforce that:
```ruby
class DinnerController
@@ -834,7 +857,7 @@ class DinnerController
end
```
-Just like the filter, you could also passing `:only` and `:except` to enforce the secure connection only to specific actions:
+Just like the filter, you could also pass `:only` and `:except` to enforce the secure connection only to specific actions:
```ruby
class DinnerController
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index fb26a3a6a3..795afd0150 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -3,9 +3,13 @@ Action Mailer Basics
This guide should provide you with all you need to get started in sending and receiving emails from and to your application, and many internals of Action Mailer. It also covers how to test your mailers.
---------------------------------------------------------------------------------
+After reading this guide, you will know:
-WARNING. This guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
+* How to send and receive email within a Rails application.
+* How to generate and edit an Action Mailer class and mailer view.
+* How to configure Action Mailer for your environment.
+* How to test your Action Mailer classes.
+--------------------------------------------------------------------------------
Introduction
------------
@@ -105,7 +109,7 @@ When you call the `mail` method now, Action Mailer will detect the two templates
#### Wire It Up So That the System Sends the Email When a User Signs Up
-There are several ways to do this, some people create Rails Observers to fire off emails, others do it inside of the User Model. However, in Rails 3, mailers are really just another way to render a view. Instead of rendering a view and sending out the HTTP protocol, they are just sending it out through the Email protocols instead. Due to this, it makes sense to just have your controller tell the mailer to send an email when a user is successfully created.
+There are several ways to do this, some people create Rails Observers to fire off emails, others do it inside of the User Model. However, mailers are really just another way to render a view. Instead of rendering a view and sending out the HTTP protocol, they are just sending it out through the Email protocols instead. Due to this, it makes sense to just have your controller tell the mailer to send an email when a user is successfully created.
Setting this up is painfully simple.
@@ -145,10 +149,6 @@ This provides a much simpler implementation that does not require the registerin
The method `welcome_email` returns a `Mail::Message` object which can then just be told `deliver` to send itself out.
-NOTE: In previous versions of Rails, you would call `deliver_welcome_email` or `create_welcome_email`. This has been deprecated in Rails 3.0 in favour of just calling the method name itself.
-
-WARNING: Sending out an email should only take a fraction of a second. If you are planning on sending out many emails, or you have a slow domain resolution service, you might want to investigate using a background process like Delayed Job.
-
### Auto encoding header values
Action Mailer now handles the auto encoding of multibyte characters inside of headers and bodies.
@@ -377,23 +377,7 @@ If you use this setting, you should pass the `only_path: false` option when usin
Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have `welcome_email.text.erb` and `welcome_email.html.erb` in `app/views/user_mailer`, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts.
-The order of the parts getting inserted is determined by the `:parts_order` inside of the `ActionMailer::Base.default` method. If you want to explicitly alter the order, you can either change the `:parts_order` or explicitly render the parts in a different order:
-
-```ruby
-class UserMailer < ActionMailer::Base
- def welcome_email(user)
- @user = user
- @url = user_url(@user)
- mail(to: user.email,
- subject: 'Welcome to My Awesome Site') do |format|
- format.html
- format.text
- end
- end
-end
-```
-
-Will put the HTML part first, and the plain text part second.
+The order of the parts getting inserted is determined by the `:parts_order` inside of the `ActionMailer::Base.default` method.
### Sending Emails with Attachments
@@ -413,7 +397,7 @@ end
The above will send a multipart email with an attachment, properly nested with the top level being `multipart/mixed` and the first part being a `multipart/alternative` containing the plain text and HTML email messages.
-#### Sending Emails with Dynamic Delivery Options
+### Sending Emails with Dynamic Delivery Options
If you wish to override the default delivery options (e.g. SMTP credentials) while delivering emails, you can do this using `delivery_method_options` in the mailer action.
@@ -460,6 +444,57 @@ class UserMailer < ActionMailer::Base
end
```
+Action Mailer Callbacks
+---------------------------
+
+Action Mailer allows for you to specify a `before_action`, `after_action` and 'around_action'.
+
+* Filters can be specified with a block or a symbol to a method in the mailer class similar to controllers.
+
+* You could use a `before_action` to prepopulate the mail object with defaults, delivery_method_options or insert default headers and attachments.
+
+* You could use an `after_action` to do similar setup as a `before_action` but using instance variables set in your mailer action.
+
+```ruby
+class UserMailer < ActionMailer::Base
+ after_action :set_delivery_options, :prevent_delivery_to_guests, :set_business_headers
+
+ def feedback_message(business, user)
+ @business = business
+ @user = user
+ mail
+ end
+
+ def campaign_message(business, user)
+ @business = business
+ @user = user
+ end
+
+ private
+
+ def set_delivery_options
+ # You have access to the mail instance and @business and @user instance variables here
+ if @business && @business.has_smtp_settings?
+ mail.delivery_method.settings.merge!(@business.smtp_settings)
+ end
+ end
+
+ def prevent_delivery_to_guests
+ if @user && @user.guest?
+ mail.perform_deliveries = false
+ end
+ end
+
+ def set_business_headers
+ if @business
+ headers["X-SMTPAPI-CATEGORY"] = @business.code
+ end
+ end
+end
+```
+
+* Mailer Filters abort further processing if body is set to a non-nil value.
+
Using Action Mailer Helpers
---------------------------
@@ -481,7 +516,6 @@ The following configuration options are best made in one of the environment file
|`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.|
|`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.|
|`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).|
-|`async`|Setting this flag will turn on asynchronous message sending, message rendering and delivery will be pushed to `Rails.queue` for processing.|
### Example Action Mailer Configuration
@@ -541,26 +575,3 @@ end
```
In the test we send the email and store the returned object in the `email` variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect.
-
-Asynchronous
-------------
-
-Rails provides a Synchronous Queue by default. If you want to use an Asynchronous one you will need to configure an async Queue provider like Resque. Queue providers are supposed to have a Railtie where they configure it's own async queue.
-
-### Custom Queues
-
-If you need a different queue than `Rails.queue` for your mailer you can use `ActionMailer::Base.queue=`:
-
-```ruby
-class WelcomeMailer < ActionMailer::Base
- self.queue = MyQueue.new
-end
-```
-
-or adding to your `config/environments/$RAILS_ENV.rb`:
-
-```ruby
-config.action_mailer.queue = MyQueue.new
-```
-
-Your custom queue should expect a job that responds to `#run`.
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index 2625e237bf..4cdac43a7e 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -1,13 +1,13 @@
Action View Overview
====================
-In this guide you will learn:
+After reading this guide, you will know:
-* What Action View is and how to use it with Rails
-* How best to use templates, partials, and layouts
-* What helpers are provided by Action View and how to make your own
-* How to use localized views
-* How to use Action View outside of Rails
+* What Action View is and how to use it with Rails.
+* How best to use templates, partials, and layouts.
+* What helpers are provided by Action View and how to make your own.
+* How to use localized views.
+* How to use Action View outside of Rails.
--------------------------------------------------------------------------------
@@ -1263,8 +1263,6 @@ Creates a field set for grouping HTML form elements.
Creates a file upload field.
-Prior to Rails 3.1, if you are using file uploads, then you will need to set the multipart option for the form tag. Rails 3.1+ does this automatically.
-
```html+erb
<%= form_tag {action: "post"}, {multipart: true} do %>
<label for="file">File to Upload</label> <%= file_field_tag "file" %>
@@ -1486,7 +1484,7 @@ You can use the same technique to localize the rescue files in your public direc
Since Rails doesn't restrict the symbols that you use to set I18n.locale, you can leverage this system to display different content depending on anything you like. For example, suppose you have some "expert" users that should see different pages from "normal" users. You could add the following to `app/controllers/application.rb`:
```ruby
-before_filter :set_expert_locale
+before_action :set_expert_locale
def set_expert_locale
I18n.locale = :expert if current_user.expert?
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index 92b51334a3..68ac26c681 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -1,16 +1,16 @@
Active Model Basics
===================
-This guide should provide you with all you need to get started using model classes. Active Model allows for Action Pack helpers to interact with non-ActiveRecord models. Active Model also helps building custom ORMs for use outside of the Rails framework.
+This guide should provide you with all you need to get started using model classes. Active Model allows for Action Pack helpers to interact with non-Active Record models. Active Model also helps building custom ORMs for use outside of the Rails framework.
---------------------------------------------------------------------------------
+After reading this guide, you will know:
-WARNING. This guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+--------------------------------------------------------------------------------
Introduction
------------
-Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below.
+Active Model is a library containing various modules used in developing frameworks that need to interact with the Rails Action Pack library. Active Model provides a known set of interfaces for usage in classes. Some of modules are explained below.
### AttributeMethods
@@ -26,23 +26,21 @@ class Person
attr_accessor :age
-private
- def reset_attribute(attribute)
- send("#{attribute}=", 0)
- end
+ private
+ def reset_attribute(attribute)
+ send("#{attribute}=", 0)
+ end
- def attribute_highest?(attribute)
- send(attribute) > 100 ? true : false
- end
-
+ def attribute_highest?(attribute)
+ send(attribute) > 100
+ end
end
person = Person.new
person.age = 110
person.age_highest? # true
person.reset_age # 0
-person.age_highest? # false
-
+person.age_highest? # false
```
### Callbacks
@@ -87,14 +85,14 @@ class Person
end
person = Person.new
-person.to_model == person #=> true
-person.to_key #=> nil
-person.to_param #=> nil
+person.to_model == person # => true
+person.to_key # => nil
+person.to_param # => nil
```
### Dirty
-An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Let's consider a Person class with attributes first_name and last_name
+An object becomes dirty when it has gone through one or more changes to its attributes and has not been saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Let's consider a Person class with attributes `first_name` and `last_name`:
```ruby
require 'active_model'
@@ -123,8 +121,8 @@ class Person
def save
@previously_changed = changes
+ # do save work...
end
-
end
```
@@ -132,21 +130,22 @@ end
```ruby
person = Person.new
-person.first_name = "First Name"
+person.changed? # => false
-person.first_name #=> "First Name"
-person.first_name = "First Name Changed"
+person.first_name = "First Name"
+person.first_name # => "First Name"
-person.changed? #=> true
+# returns if any attribute has changed.
+person.changed? # => true
-#returns an list of fields arry which all has been changed before saved.
-person.changed #=> ["first_name"]
+# returns a list of attributes that have changed before saving.
+person.changed # => ["first_name"]
-#returns a hash of the fields that have changed with their original values.
-person.changed_attributes #=> {"first_name" => "First Name Changed"}
+# returns a hash of the attributes that have changed with their original values.
+person.changed_attributes # => {"first_name"=>nil}
-#returns a hash of changes, with the attribute names as the keys, and the values will be an array of the old and new value for that field.
-person.changes #=> {"first_name" => ["First Name","First Name Changed"]}
+# returns a hash of changes, with the attribute names as the keys, and the values will be an array of the old and new value for that field.
+person.changes # => {"first_name"=>[nil, "First Name"]}
```
#### Attribute based accessor methods
@@ -154,28 +153,24 @@ person.changes #=> {"first_name" => ["First Name","First Name Changed"]}
Track whether the particular attribute has been changed or not.
```ruby
-#attr_name_changed?
-person.first_name #=> "First Name"
-
-#assign some other value to first_name attribute
-person.first_name = "First Name 1"
-
-person.first_name_changed? #=> true
+# attr_name_changed?
+person.first_name # => "First Name"
+person.first_name_changed? # => true
```
Track what was the previous value of the attribute.
```ruby
-#attr_name_was accessor
-person.first_name_was #=> "First Name"
+# attr_name_was accessor
+person.first_name_was # => "First Name"
```
Track both previous and current value of the changed attribute. Returns an array if changed, else returns nil.
```ruby
-#attr_name_change
-person.first_name_change #=> ["First Name", "First Name 1"]
-person.last_name_change #=> nil
+# attr_name_change
+person.first_name_change # => [nil, "First Name"]
+person.last_name_change # => nil
```
### Validations
@@ -187,20 +182,19 @@ class Person
include ActiveModel::Validations
attr_accessor :name, :email, :token
-
+
validates :name, presence: true
- validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
+ validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
validates! :token, presence: true
-
end
person = Person.new(token: "2b1f325")
-person.valid? #=> false
-person.name = 'vishnu'
-person.email = 'me'
-person.valid? #=> false
+person.valid? # => false
+person.name = 'vishnu'
+person.email = 'me'
+person.valid? # => false
person.email = 'me@vishnuatrai.com'
-person.valid? #=> true
+person.valid? # => true
person.token = nil
-person.valid? #=> raises ActiveModel::StrictValidationFailed
+person.valid? # => raises ActiveModel::StrictValidationFailed
```
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 810a0263c0..c90f42c492 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -1,32 +1,51 @@
Active Record Basics
====================
-This guide is an introduction to Active Record. After reading this guide we hope that you'll learn:
+This guide is an introduction to Active Record.
-* What Object Relational Mapping and Active Record are and how they are used in Rails
-* How Active Record fits into the Model-View-Controller paradigm
-* How to use Active Record models to manipulate data stored in a relational database
-* Active Record schema naming conventions
-* The concepts of database migrations, validations and callbacks
+After reading this guide, you will know:
+
+* What Object Relational Mapping and Active Record are and how they are used in
+ Rails.
+* How Active Record fits into the Model-View-Controller paradigm.
+* How to use Active Record models to manipulate data stored in a relational
+ database.
+* Active Record schema naming conventions.
+* The concepts of database migrations, validations and callbacks.
--------------------------------------------------------------------------------
What is Active Record?
----------------------
-Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the model - which is the layer of the system responsible for representing business data and logic. Active Record facilitates the creation and use of business objects whose data requires persistent storage to a database. It is an implementation of the Active Record pattern which itself is a description of an Object Relational Mapping system.
+Active Record is the M in [MVC](getting_started.html#the-mvc-architecture) - the
+model - which is the layer of the system responsible for representing business
+data and logic. Active Record facilitates the creation and use of business
+objects whose data requires persistent storage to a database. It is an
+implementation of the Active Record pattern which itself is a description of an
+Object Relational Mapping system.
### The Active Record Pattern
-Active Record was described by Martin Fowler in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring data access logic is part of the object will educate users of that object on how to write to and read from the database.
+Active Record was described by Martin Fowler in his book _Patterns of Enterprise
+Application Architecture_. In Active Record, objects carry both persistent data
+and behavior which operates on that data. Active Record takes the opinion that
+ensuring data access logic is part of the object will educate users of that
+object on how to write to and read from the database.
### Object Relational Mapping
-Object-Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and retrieved from a database without writing SQL statements directly and with less overall database access code.
+Object-Relational Mapping, commonly referred to as its abbreviation ORM, is
+a technique that connects the rich objects of an application to tables in
+a relational database management system. Using ORM, the properties and
+relationships of the objects in an application can be easily stored and
+retrieved from a database without writing SQL statements directly and with less
+overall database access code.
### Active Record as an ORM Framework
-Active Record gives us several mechanisms, the most important being the ability to:
+Active Record gives us several mechanisms, the most important being the ability
+to:
* Represent models and their data
* Represent associations between these models
@@ -37,14 +56,30 @@ Active Record gives us several mechanisms, the most important being the ability
Convention over Configuration in Active Record
----------------------------------------------
-When writing applications using other programming languages or frameworks, it may be necessary to write a lot of configuration code. This is particularly true for ORM frameworks in general. However, if you follow the conventions adopted by Rails, you'll need to write very little configuration (in some case no configuration at all) when creating Active Record models. The idea is that if you configure your applications in the very same way most of the times then this should be the default way. In this cases, explicit configuration would be needed only in those cases where you can't follow the conventions for any reason.
+When writing applications using other programming languages or frameworks, it
+may be necessary to write a lot of configuration code. This is particularly true
+for ORM frameworks in general. However, if you follow the conventions adopted by
+Rails, you'll need to write very little configuration (in some case no
+configuration at all) when creating Active Record models. The idea is that if
+you configure your applications in the very same way most of the times then this
+should be the default way. In this cases, explicit configuration would be needed
+only in those cases where you can't follow the conventions for any reason.
### Naming Conventions
-By default, Active Record uses some naming conventions to find out how the mapping between models and database tables should be created. Rails will pluralize your class names to find the respective database table. So, for a class `Book`, you should have a database table called **books**. The Rails pluralization mechanisms are very powerful, being capable to pluralize (and singularize) both regular and irregular words. When using class names composed of two or more words, the model class name should follow the Ruby conventions, using the CamelCase form, while the table name must contain the words separated by underscores. Examples:
+By default, Active Record uses some naming conventions to find out how the
+mapping between models and database tables should be created. Rails will
+pluralize your class names to find the respective database table. So, for
+a class `Book`, you should have a database table called **books**. The Rails
+pluralization mechanisms are very powerful, being capable to pluralize (and
+singularize) both regular and irregular words. When using class names composed
+of two or more words, the model class name should follow the Ruby conventions,
+using the CamelCase form, while the table name must contain the words separated
+by underscores. Examples:
* Database Table - Plural with underscores separating words (e.g., `book_clubs`)
-* Model Class - Singular with the first letter of each word capitalized (e.g., `BookClub`)
+* Model Class - Singular with the first letter of each word capitalized (e.g.,
+`BookClub`)
| Model / Class | Table / Schema |
| ------------- | -------------- |
@@ -57,34 +92,52 @@ By default, Active Record uses some naming conventions to find out how the mappi
### Schema Conventions
-Active Record uses naming conventions for the columns in database tables, depending on the purpose of these columns.
-
-* **Foreign keys** - These fields should be named following the pattern `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the fields that Active Record will look for when you create associations between your models.
-* **Primary keys** - By default, Active Record will use an integer column named `id` as the table's primary key. When using [Rails Migrations](migrations.html) to create your tables, this column will be automatically created.
-
-There are also some optional column names that will create additional features to Active Record instances:
-
-* `created_at` - Automatically gets set to the current date and time when the record is first created.
-* `created_on` - Automatically gets set to the current date when the record is first created.
-* `updated_at` - Automatically gets set to the current date and time whenever the record is updated.
-* `updated_on` - Automatically gets set to the current date whenever the record is updated.
-* `lock_version` - Adds [optimistic locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to a model.
-* `type` - Specifies that the model uses [Single Table Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
-* `(table_name)_count` - Used to cache the number of belonging objects on associations. For example, a `comments_count` column in a `Post` class that has many instances of `Comment` will cache the number of existent comments for each post.
+Active Record uses naming conventions for the columns in database tables,
+depending on the purpose of these columns.
+
+* **Foreign keys** - These fields should be named following the pattern
+ `singularized_table_name_id` (e.g., `item_id`, `order_id`). These are the
+ fields that Active Record will look for when you create associations between
+ your models.
+* **Primary keys** - By default, Active Record will use an integer column named
+ `id` as the table's primary key. When using [Rails
+ Migrations](migrations.html) to create your tables, this column will be
+ automatically created.
+
+There are also some optional column names that will create additional features
+to Active Record instances:
+
+* `created_at` - Automatically gets set to the current date and time when the
+ record is first created.
+* `updated_at` - Automatically gets set to the current date and time whenever
+ the record is updated.
+* `lock_version` - Adds [optimistic
+ locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to
+ a model.
+* `type` - Specifies that the model uses [Single Table
+ Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
+* `(table_name)_count` - Used to cache the number of belonging objects on
+ associations. For example, a `comments_count` column in a `Post` class that
+ has many instances of `Comment` will cache the number of existent comments
+ for each post.
NOTE: While these column names are optional, they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, `type` is a reserved keyword used to designate a table using Single Table Inheritance (STI). If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling.
Creating Active Record Models
-----------------------------
-It is very easy to create Active Record models. All you have to do is to subclass the `ActiveRecord::Base` class and you're good to go:
+It is very easy to create Active Record models. All you have to do is to
+subclass the `ActiveRecord::Base` class and you're good to go:
```ruby
class Product < ActiveRecord::Base
end
```
-This will create a `Product` model, mapped to a `products` table at the database. By doing this you'll also have the ability to map the columns of each row in that table with the attributes of the instances of your model. Suppose that the `products` table was created using an SQL sentence like:
+This will create a `Product` model, mapped to a `products` table at the
+database. By doing this you'll also have the ability to map the columns of each
+row in that table with the attributes of the instances of your model. Suppose
+that the `products` table was created using an SQL sentence like:
```sql
CREATE TABLE products (
@@ -94,7 +147,8 @@ CREATE TABLE products (
);
```
-Following the table schema above, you would be able to write code like the following:
+Following the table schema above, you would be able to write code like the
+following:
```ruby
p = Product.new
@@ -105,9 +159,12 @@ puts p.name # "Some Book"
Overriding the Naming Conventions
---------------------------------
-What if you need to follow a different naming convention or need to use your Rails application with a legacy database? No problem, you can easily override the default conventions.
+What if you need to follow a different naming convention or need to use your
+Rails application with a legacy database? No problem, you can easily override
+the default conventions.
-You can use the `ActiveRecord::Base.table_name=` method to specify the table name that should be used:
+You can use the `ActiveRecord::Base.table_name=` method to specify the table
+name that should be used:
```ruby
class Product < ActiveRecord::Base
@@ -115,7 +172,9 @@ class Product < ActiveRecord::Base
end
```
-If you do so, you will have to define manually the class name that is hosting the fixtures (class_name.yml) using the `set_fixture_class` method in your test definition:
+If you do so, you will have to define manually the class name that is hosting
+the fixtures (class_name.yml) using the `set_fixture_class` method in your test
+definition:
```ruby
class FunnyJoke < ActiveSupport::TestCase
@@ -125,7 +184,8 @@ class FunnyJoke < ActiveSupport::TestCase
end
```
-It's also possible to override the column that should be used as the table's primary key using the `ActiveRecord::Base.set_primary_key` method:
+It's also possible to override the column that should be used as the table's
+primary key using the `ActiveRecord::Base.set_primary_key` method:
```ruby
class Product < ActiveRecord::Base
@@ -136,93 +196,175 @@ end
CRUD: Reading and Writing Data
------------------------------
-CRUD is an acronym for the four verbs we use to operate on data: **C**reate, **R**ead, **U**pdate and **D**elete. Active Record automatically creates methods to allow an application to read and manipulate data stored within its tables.
+CRUD is an acronym for the four verbs we use to operate on data: **C**reate,
+**R**ead, **U**pdate and **D**elete. Active Record automatically creates methods
+to allow an application to read and manipulate data stored within its tables.
### Create
-Active Record objects can be created from a hash, a block or have their attributes manually set after creation. The `new` method will return a new object while `create` will return the object and save it to the database.
+Active Record objects can be created from a hash, a block or have their
+attributes manually set after creation. The `new` method will return a new
+object while `create` will return the object and save it to the database.
-For example, given a model `User` with attributes of `name` and `occupation`, the `create` method call will create and save a new record into the database:
+For example, given a model `User` with attributes of `name` and `occupation`,
+the `create` method call will create and save a new record into the database:
```ruby
- user = User.create(name: "David", occupation: "Code Artist")
+user = User.create(name: "David", occupation: "Code Artist")
```
-Using the `new` method, an object can be created without being saved:
+Using the `new` method, an object can be instantiated without being saved:
```ruby
- user = User.new
- user.name = "David"
- user.occupation = "Code Artist"
+user = User.new
+user.name = "David"
+user.occupation = "Code Artist"
```
A call to `user.save` will commit the record to the database.
-Finally, if a block is provided, both `create` and `new` will yield the new object to that block for initialization:
+Finally, if a block is provided, both `create` and `new` will yield the new
+object to that block for initialization:
```ruby
- user = User.new do |u|
- u.name = "David"
- u.occupation = "Code Artist"
- end
+user = User.new do |u|
+ u.name = "David"
+ u.occupation = "Code Artist"
+end
```
### Read
-Active Record provides a rich API for accessing data within a database. Below are a few examples of different data access methods provided by Active Record.
+Active Record provides a rich API for accessing data within a database. Below
+are a few examples of different data access methods provided by Active Record.
```ruby
- # return array with all records
- users = User.all
+# return array with all records
+users = User.all
```
```ruby
- # return the first record
- user = User.first
+# return the first record
+user = User.first
```
```ruby
- # return the first user named David
- david = User.find_by_name('David')
+# return the first user named David
+david = User.find_by_name('David')
```
```ruby
- # find all users named David who are Code Artists and sort by created_at in reverse chronological order
- users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC')
+# find all users named David who are Code Artists and sort by created_at in reverse chronological order
+users = User.where(name: 'David', occupation: 'Code Artist').order('created_at DESC')
```
-You can learn more about querying an Active Record model in the [Active Record Query Interface](active_record_querying.html) guide.
+You can learn more about querying an Active Record model in the [Active Record
+Query Interface](active_record_querying.html) guide.
### Update
-Once an Active Record object has been retrieved, its attributes can be modified and it can be saved to the database.
+Once an Active Record object has been retrieved, its attributes can be modified
+and it can be saved to the database.
+
+```ruby
+user = User.find_by_name('David')
+user.name = 'Dave'
+user.save
+```
+
+A shorthand for this is to use a hash mapping attribute names to the desired
+value, like so:
+
+```ruby
+user = User.find_by_name('David')
+user.update(name: 'Dave')
+```
+
+This is most useful when updating several attributes at once. If, on the other
+hand, you'd like to update several records in bulk, you may find the
+`update_all` class method useful:
```ruby
- user = User.find_by_name('David')
- user.name = 'Dave'
- user.save
+User.update_all "max_login_attempts = 3, must_change_password = 'true'"
```
### Delete
-Likewise, once retrieved an Active Record object can be destroyed which removes it from the database.
+Likewise, once retrieved an Active Record object can be destroyed which removes
+it from the database.
```ruby
- user = User.find_by_name('David')
- user.destroy
+user = User.find_by_name('David')
+user.destroy
```
Validations
-----------
-Active Record allows you to validate the state of a model before it gets written into the database. There are several methods that you can use to check your models and validate that an attribute value is not empty, is unique and not already in the database, follows a specific format and many more. You can learn more about validations in the [Active Record Validations and Callbacks guide](active_record_validations_callbacks.html#validations-overview).
+Active Record allows you to validate the state of a model before it gets written
+into the database. There are several methods that you can use to check your
+models and validate that an attribute value is not empty, is unique and not
+already in the database, follows a specific format and many more.
+
+Validation is a very important issue to consider when persisting to database, so
+the methods `create`, `save` and `update` take it into account when
+running: they return `false` when validation fails and they didn't actually
+perform any operation on database. All of these have a bang counterpart (that
+is, `create!`, `save!` and `update!`), which are stricter in that
+they raise the exception `ActiveRecord::RecordInvalid` if validation fails.
+A quick example to illustrate:
+
+```ruby
+class User < ActiveRecord::Base
+ validates_presence_of :name
+end
+
+User.create # => false
+User.create! # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
+```
+
+You can learn more about validations in the [Active Record Validations
+guide](active_record_validations.html).
Callbacks
---------
-Active Record callbacks allow you to attach code to certain events in the life-cycle of your models. This enables you to add behavior to your models by transparently executing code when those events occur, like when you create a new record, update it, destroy it and so on. You can learn more about callbacks in the [Active Record Validations and Callbacks guide](active_record_validations_callbacks.html#callbacks-overview).
+Active Record callbacks allow you to attach code to certain events in the
+life-cycle of your models. This enables you to add behavior to your models by
+transparently executing code when those events occur, like when you create a new
+record, update it, destroy it and so on. You can learn more about callbacks in
+the [Active Record Callbacks guide](active_record_callbacks.html).
Migrations
----------
-Rails provides a domain-specific language for managing a database schema called migrations. Migrations are stored in files which are executed against any database that Active Record support using rake. Rails keeps track of which files have been committed to the database and provides rollback features. You can learn more about migrations in the [Active Record Migrations guide](migrations.html)
+Rails provides a domain-specific language for managing a database schema called
+migrations. Migrations are stored in files which are executed against any
+database that Active Record support using `rake`. Here's a migration that
+creates a table:
+
+```ruby
+class CreatePublications < ActiveRecord::Migration
+ def change
+ create_table :publications do |t|
+ t.string :title
+ t.text :description
+ t.references :publication_type
+ t.integer :publisher_id
+ t.string :publisher_type
+ t.boolean :single_issue
+
+ t.timestamps
+ end
+ add_index :publications, :publication_type_id
+ end
+end
+```
+
+Rails keeps track of which files have been committed to the database and
+provides rollback features. To actually create the table, you'd run `rake db:migrate`
+and to roll it back, `rake db:rollback`.
+
+Note that the above code is database-agnostic: it will run in MySQL, postgresql,
+Oracle and others. You can learn more about migrations in the [Active Record
+Migrations guide](migrations.html)
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
new file mode 100644
index 0000000000..20959a1a35
--- /dev/null
+++ b/guides/source/active_record_callbacks.md
@@ -0,0 +1,362 @@
+Active Record Callbacks
+=======================
+
+This guide teaches you how to hook into the life cycle of your Active Record
+objects.
+
+After reading this guide, you will know:
+
+* The life cycle of Active Record objects.
+* How to create callback methods that respond to events in the object life cycle.
+* How to create special classes that encapsulate common behavior for your callbacks.
+
+--------------------------------------------------------------------------------
+
+The Object Life Cycle
+---------------------
+
+During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object life cycle</em> so that you can control your application and its data.
+
+Callbacks allow you to trigger logic before or after an alteration of an object's state.
+
+Callbacks Overview
+------------------
+
+Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it is possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
+
+### Callback Registration
+
+In order to use the available callbacks, you need to register them. You can implement the callbacks as ordinary methods and use a macro-style class method to register them as callbacks:
+
+```ruby
+class User < ActiveRecord::Base
+ validates :login, :email, presence: true
+
+ before_validation :ensure_login_has_a_value
+
+ protected
+ def ensure_login_has_a_value
+ if login.nil?
+ self.login = email unless email.blank?
+ end
+ end
+end
+```
+
+The macro-style class methods can also receive a block. Consider using this style if the code inside your block is so short that it fits in a single line:
+
+```ruby
+class User < ActiveRecord::Base
+ validates :login, :email, presence: true
+
+ before_create do |user|
+ user.name = user.login.capitalize if user.name.blank?
+ end
+end
+```
+
+Callbacks can also be registered to only fire on certain lifecycle events:
+
+```ruby
+class User < ActiveRecord::Base
+ before_validation :normalize_name, on: :create
+
+ # :on takes an array as well
+ after_validation :set_location, on: [ :create, :update ]
+
+ protected
+ def normalize_name
+ self.name = self.name.downcase.titleize
+ end
+
+ def set_location
+ self.location = LocationService.query(self)
+ end
+end
+```
+
+It is considered good practice to declare callback methods as protected or private. If left public, they can be called from outside of the model and violate the principle of object encapsulation.
+
+Available Callbacks
+-------------------
+
+Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations:
+
+### Creating an Object
+
+* `before_validation`
+* `after_validation`
+* `before_save`
+* `around_save`
+* `before_create`
+* `around_create`
+* `after_create`
+* `after_save`
+
+### Updating an Object
+
+* `before_validation`
+* `after_validation`
+* `before_save`
+* `around_save`
+* `before_update`
+* `around_update`
+* `after_update`
+* `after_save`
+
+### Destroying an Object
+
+* `before_destroy`
+* `around_destroy`
+* `after_destroy`
+
+WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
+
+### `after_initialize` and `after_find`
+
+The `after_initialize` callback will be called whenever an Active Record object is instantiated, either by directly using `new` or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record `initialize` method.
+
+The `after_find` callback will be called whenever Active Record loads a record from the database. `after_find` is called before `after_initialize` if both are defined.
+
+The `after_initialize` and `after_find` callbacks have no `before_*` counterparts, but they can be registered just like the other Active Record callbacks.
+
+```ruby
+class User < ActiveRecord::Base
+ after_initialize do |user|
+ puts "You have initialized an object!"
+ end
+
+ after_find do |user|
+ puts "You have found an object!"
+ end
+end
+
+>> User.new
+You have initialized an object!
+=> #<User id: nil>
+
+>> User.first
+You have found an object!
+You have initialized an object!
+=> #<User id: 1>
+```
+
+Running Callbacks
+-----------------
+
+The following methods trigger callbacks:
+
+* `create`
+* `create!`
+* `decrement!`
+* `destroy`
+* `destroy!`
+* `destroy_all`
+* `increment!`
+* `save`
+* `save!`
+* `save(validate: false)`
+* `toggle!`
+* `update`
+* `update_attribute`
+* `update`
+* `update!`
+* `valid?`
+
+Additionally, the `after_find` callback is triggered by the following finder methods:
+
+* `all`
+* `first`
+* `find`
+* `find_all_by_*`
+* `find_by_*`
+* `find_by_*!`
+* `find_by_sql`
+* `last`
+
+The `after_initialize` callback is triggered every time a new object of the class is initialized.
+
+NOTE: The `find_all_by_*`, `find_by_*` and `find_by_*!` methods are dynamic finders generated automatically for every attribute. Learn more about them at the [Dynamic finders section](active_record_querying.html#dynamic-finders)
+
+Skipping Callbacks
+------------------
+
+Just as with validations, it is also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data.
+
+* `decrement`
+* `decrement_counter`
+* `delete`
+* `delete_all`
+* `increment`
+* `increment_counter`
+* `toggle`
+* `touch`
+* `update_column`
+* `update_columns`
+* `update_all`
+* `update_counters`
+
+Halting Execution
+-----------------
+
+As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
+
+The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception.
+
+WARNING. Raising an arbitrary exception may break code that expects `save` and its friends not to fail like that. The `ActiveRecord::Rollback` exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised.
+
+Relational Callbacks
+--------------------
+
+Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many posts. A user's posts should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Post` model:
+
+```ruby
+class User < ActiveRecord::Base
+ has_many :posts, dependent: :destroy
+end
+
+class Post < ActiveRecord::Base
+ after_destroy :log_destroy_action
+
+ def log_destroy_action
+ puts 'Post destroyed'
+ end
+end
+
+>> user = User.first
+=> #<User id: 1>
+>> user.posts.create!
+=> #<Post id: 1, user_id: 1>
+>> user.destroy
+Post destroyed
+=> #<User id: 1>
+```
+
+Conditional Callbacks
+---------------------
+
+As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the `:if` and `:unless` options, which can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if` option when you want to specify under which conditions the callback **should** be called. If you want to specify the conditions under which the callback **should not** be called, then you may use the `:unless` option.
+
+### Using `:if` and `:unless` with a `Symbol`
+
+You can associate the `:if` and `:unless` options with a symbol corresponding to the name of a predicate method that will get called right before the callback. When using the `:if` option, the callback won't be executed if the predicate method returns false; when using the `:unless` option, the callback won't be executed if the predicate method returns true. This is the most common option. Using this form of registration it is also possible to register several different predicates that should be called to check if the callback should be executed.
+
+```ruby
+class Order < ActiveRecord::Base
+ before_save :normalize_card_number, if: :paid_with_card?
+end
+```
+
+### Using `:if` and `:unless` with a String
+
+You can also use a string that will be evaluated using `eval` and hence needs to contain valid Ruby code. You should use this option only when the string represents a really short condition:
+
+```ruby
+class Order < ActiveRecord::Base
+ before_save :normalize_card_number, if: "paid_with_card?"
+end
+```
+
+### Using `:if` and `:unless` with a `Proc`
+
+Finally, it is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners:
+
+```ruby
+class Order < ActiveRecord::Base
+ before_save :normalize_card_number,
+ if: Proc.new { |order| order.paid_with_card? }
+end
+```
+
+### Multiple Conditions for Callbacks
+
+When writing conditional callbacks, it is possible to mix both `:if` and `:unless` in the same callback declaration:
+
+```ruby
+class Comment < ActiveRecord::Base
+ after_create :send_email_to_author, if: :author_wants_emails?,
+ unless: Proc.new { |comment| comment.post.ignore_comments? }
+end
+```
+
+Callback Classes
+----------------
+
+Sometimes the callback methods that you'll write will be useful enough to be reused by other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them.
+
+Here's an example where we create a class with an `after_destroy` callback for a `PictureFile` model:
+
+```ruby
+class PictureFileCallbacks
+ def after_destroy(picture_file)
+ if File.exists?(picture_file.filepath)
+ File.delete(picture_file.filepath)
+ end
+ end
+end
+```
+
+When declared inside a class, as above, the callback methods will receive the model object as a parameter. We can now use the callback class in the model:
+
+```ruby
+class PictureFile < ActiveRecord::Base
+ after_destroy PictureFileCallbacks.new
+end
+```
+
+Note that we needed to instantiate a new `PictureFileCallbacks` object, since we declared our callback as an instance method. This is particularly useful if the callbacks make use of the state of the instantiated object. Often, however, it will make more sense to declare the callbacks as class methods:
+
+```ruby
+class PictureFileCallbacks
+ def self.after_destroy(picture_file)
+ if File.exists?(picture_file.filepath)
+ File.delete(picture_file.filepath)
+ end
+ end
+end
+```
+
+If the callback method is declared this way, it won't be necessary to instantiate a `PictureFileCallbacks` object.
+
+```ruby
+class PictureFile < ActiveRecord::Base
+ after_destroy PictureFileCallbacks
+end
+```
+
+You can declare as many callbacks as you want inside your callback classes.
+
+Transaction Callbacks
+---------------------
+
+There are two additional callbacks that are triggered by the completion of a database transaction: `after_commit` and `after_rollback`. These callbacks are very similar to the `after_save` callback except that they don't execute until after database changes have either been committed or rolled back. They are most useful when your active record models need to interact with external systems which are not part of the database transaction.
+
+Consider, for example, the previous example where the `PictureFile` model needs to delete a file after the corresponding record is destroyed. If anything raises an exception after the `after_destroy` callback is called and the transaction rolls back, the file will have been deleted and the model will be left in an inconsistent state. For example, suppose that `picture_file_2` in the code below is not valid and the `save!` method raises an error.
+
+```ruby
+PictureFile.transaction do
+ picture_file_1.destroy
+ picture_file_2.save!
+end
+```
+
+By using the `after_commit` callback we can account for this case.
+
+```ruby
+class PictureFile < ActiveRecord::Base
+ attr_accessor :delete_file
+
+ after_destroy do |picture_file|
+ picture_file.delete_file = picture_file.filepath
+ end
+
+ after_commit do |picture_file|
+ if picture_file.delete_file && File.exist?(picture_file.delete_file)
+ File.delete(picture_file.delete_file)
+ picture_file.delete_file = nil
+ end
+ end
+end
+```
+
+The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback.
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 79d00ded0a..24f98f68ca 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -1,15 +1,17 @@
Active Record Query Interface
=============================
-This guide covers different ways to retrieve data from the database using Active Record. By referring to this guide, you will be able to:
+This guide covers different ways to retrieve data from the database using Active Record.
-* Find records using a variety of methods and conditions
-* Specify the order, retrieved attributes, grouping, and other properties of the found records
-* Use eager loading to reduce the number of database queries needed for data retrieval
-* Use dynamic finders methods
-* Check for the existence of particular records
-* Perform various calculations on Active Record models
-* Run EXPLAIN on relations
+After reading this guide, you will know:
+
+* How to find records using a variety of methods and conditions.
+* How to specify the order, retrieved attributes, grouping, and other properties of the found records.
+* How to use eager loading to reduce the number of database queries needed for data retrieval.
+* How to use dynamic finders methods.
+* How to check for the existence of particular records.
+* How to perform various calculations on Active Record models.
+* How to run EXPLAIN on relations.
--------------------------------------------------------------------------------
@@ -466,7 +468,7 @@ The field name can also be a string:
Client.where('locked' => true)
```
-In the case of a belongs_to relationship, an association key can be used to specify the model if an ActiveRecord object is used as the value. This method works with polymorphic relationships as well.
+In the case of a belongs_to relationship, an association key can be used to specify the model if an Active Record object is used as the value. This method works with polymorphic relationships as well.
```ruby
Post.where(author: author)
@@ -503,6 +505,20 @@ This code will generate SQL like this:
SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))
```
+### NOT, LIKE, and NOT LIKE Conditions
+
+`NOT`, `LIKE`, and `NOT LIKE` SQL queries can be built by `where.not`, `where.like`, and `where.not_like` respectively.
+
+```ruby
+Post.where.not(author: author)
+
+Author.where.like(name: 'Nari%')
+
+Developer.where.not_like(name: 'Tenderl%')
+```
+
+In other words, these sort of queries can be generated by calling `where` with no argument, then immediately chain with `not`, `like`, or `not_like` passing `where` conditions.
+
Ordering
--------
@@ -985,7 +1001,7 @@ SELECT categories.* FROM categories
### Specifying Conditions on the Joined Tables
-You can specify conditions on the joined tables using the regular [Array](array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provides a special syntax for specifying conditions for the joined tables:
+You can specify conditions on the joined tables using the regular [Array](#array-conditions) and [String](#pure-string-conditions) conditions. [Hash conditions](#hash-conditions) provides a special syntax for specifying conditions for the joined tables:
```ruby
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
@@ -1188,7 +1204,7 @@ class Client < ActiveRecord::Base
end
```
-### Removing all scoping
+### Removing All Scoping
If we wish to remove scoping for any reason we can use the `unscoped` method. This is
especially useful if a `default_scope` is specified in the model and should not be
@@ -1220,9 +1236,7 @@ You can specify an exclamation point (`!`) on the end of the dynamic finders to
If you want to find both by name and locked, you can chain these finders together by simply typing "`and`" between the fields. For example, `Client.find_by_first_name_and_locked("Ryan", true)`.
-WARNING: Up to and including Rails 3.1, when the number of arguments passed to a dynamic finder method is lesser than the number of fields, say `Client.find_by_name_and_locked("Ryan")`, the behavior is to pass `nil` as the missing argument. This is **unintentional** and this behavior will be changed in Rails 3.2 to throw an `ArgumentError`.
-
-Find or build a new object
+Find or Build a New Object
--------------------------
It's common that you need to find a record or create it if it doesn't exist. You can do that with the `find_or_create_by` and `find_or_create_by!` methods.
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
new file mode 100644
index 0000000000..a911d6b941
--- /dev/null
+++ b/guides/source/active_record_validations.md
@@ -0,0 +1,1100 @@
+Active Record Validations
+=========================
+
+This guide teaches you how to validate the state of objects before they go into
+the database using Active Record's validations feature.
+
+After reading this guide, you will know:
+
+* How to use the built-in Active Record validation helpers.
+* How to create your own custom validation methods.
+* How to work with the error messages generated by the validation process.
+
+--------------------------------------------------------------------------------
+
+Validations Overview
+--------------------
+
+Here's an example of a very simple validation:
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true
+end
+
+Person.create(name: "John Doe").valid? # => true
+Person.create(name: nil).valid? # => false
+```
+
+As you can see, our validation lets us know that our `Person` is not valid
+without a `name` attribute. The second `Person` will not be persisted to the
+database.
+
+Before we dig into more details, let's talk about how validations fit into the
+big picture of your application.
+
+### Why Use Validations?
+
+Validations are used to ensure that only valid data is saved into your
+database. For example, it may be important to your application to ensure that
+every user provides a valid email address and mailing address. Model-level
+validations are the best way to ensure that only valid data is saved into your
+database. They are database agnostic, cannot be bypassed by end users, and are
+convenient to test and maintain. Rails makes them easy to use, provides
+built-in helpers for common needs, and allows you to create your own validation
+methods as well.
+
+There are several other ways to validate data before it is saved into your
+database, including native database constraints, client-side validations,
+controller-level validations. Here's a summary of the pros and cons:
+
+* Database constraints and/or stored procedures make the validation mechanisms
+ database-dependent and can make testing and maintenance more difficult.
+ However, if your database is used by other applications, it may be a good
+ idea to use some constraints at the database level. Additionally,
+ database-level validations can safely handle some things (such as uniqueness
+ in heavily-used tables) that can be difficult to implement otherwise.
+* Client-side validations can be useful, but are generally unreliable if used
+ alone. If they are implemented using JavaScript, they may be bypassed if
+ JavaScript is turned off in the user's browser. However, if combined with
+ other techniques, client-side validation can be a convenient way to provide
+ users with immediate feedback as they use your site.
+* Controller-level validations can be tempting to use, but often become
+ unwieldy and difficult to test and maintain. Whenever possible, it's a good
+ idea to keep your controllers skinny, as it will make your application a
+ pleasure to work with in the long run.
+
+Choose these in certain, specific cases. It's the opinion of the Rails team
+that model-level validations are the most appropriate in most circumstances.
+
+### When Does Validation Happen?
+
+There are two kinds of Active Record objects: those that correspond to a row
+inside your database and those that do not. When you create a fresh object, for
+example using the `new` method, that object does not belong to the database
+yet. Once you call `save` upon that object it will be saved into the
+appropriate database table. Active Record uses the `new_record?` instance
+method to determine whether an object is already in the database or not.
+Consider the following simple Active Record class:
+
+```ruby
+class Person < ActiveRecord::Base
+end
+```
+
+We can see how it works by looking at some `rails console` output:
+
+```ruby
+$ rails console
+>> p = Person.new(name: "John Doe")
+=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>
+>> p.new_record?
+=> true
+>> p.save
+=> true
+>> p.new_record?
+=> false
+```
+
+Creating and saving a new record will send an SQL `INSERT` operation to the
+database. Updating an existing record will send an SQL `UPDATE` operation
+instead. Validations are typically run before these commands are sent to the
+database. If any validations fail, the object will be marked as invalid and
+Active Record will not perform the `INSERT` or `UPDATE` operation. This avoids
+storing an invalid object in the database. You can choose to have specific
+validations run when an object is created, saved, or updated.
+
+CAUTION: There are many ways to change the state of an object in the database.
+Some methods will trigger validations, but some will not. This means that it's
+possible to save an object in the database in an invalid state if you aren't
+careful.
+
+The following methods trigger validations, and will save the object to the
+database only if the object is valid:
+
+* `create`
+* `create!`
+* `save`
+* `save!`
+* `update`
+* `update`
+* `update!`
+
+The bang versions (e.g. `save!`) raise an exception if the record is invalid.
+The non-bang versions don't: `save` and `update` return `false`,
+`create` and `update` just return the objects.
+
+### Skipping Validations
+
+The following methods skip validations, and will save the object to the
+database regardless of its validity. They should be used with caution.
+
+* `decrement!`
+* `decrement_counter`
+* `increment!`
+* `increment_counter`
+* `toggle!`
+* `touch`
+* `update_all`
+* `update_attribute`
+* `update_column`
+* `update_columns`
+* `update_counters`
+
+Note that `save` also has the ability to skip validations if passed `validate:
+false` as argument. This technique should be used with caution.
+
+* `save(validate: false)`
+
+### `valid?` and `invalid?`
+
+To verify whether or not an object is valid, Rails uses the `valid?` method.
+You can also use this method on your own. `valid?` triggers your validations
+and returns true if no errors were found in the object, and false otherwise.
+As you saw above:
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true
+end
+
+Person.create(name: "John Doe").valid? # => true
+Person.create(name: nil).valid? # => false
+```
+
+After Active Record has performed validations, any errors found can be accessed
+through the `errors` instance method, which returns a collection of errors. By
+definition, an object is valid if this collection is empty after running
+validations.
+
+Note that an object instantiated with `new` will not report errors even if it's
+technically invalid, because validations are not run when using `new`.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true
+end
+
+>> p = Person.new
+#=> #<Person id: nil, name: nil>
+>> p.errors
+#=> {}
+
+>> p.valid?
+#=> false
+>> p.errors
+#=> {name:["can't be blank"]}
+
+>> p = Person.create
+#=> #<Person id: nil, name: nil>
+>> p.errors
+#=> {name:["can't be blank"]}
+
+>> p.save
+#=> false
+
+>> p.save!
+#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
+
+>> Person.create!
+#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
+```
+
+`invalid?` is simply the inverse of `valid?`. It triggers your validations,
+returning true if any errors were found in the object, and false otherwise.
+
+### `errors[]`
+
+To verify whether or not a particular attribute of an object is valid, you can
+use `errors[:attribute]`. It returns an array of all the errors for
+`:attribute`. If there are no errors on the specified attribute, an empty array
+is returned.
+
+This method is only useful _after_ validations have been run, because it only
+inspects the errors collection and does not trigger validations itself. It's
+different from the `ActiveRecord::Base#invalid?` method explained above because
+it doesn't verify the validity of the object as a whole. It only checks to see
+whether there are errors found on an individual attribute of the object.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true
+end
+
+>> Person.new.errors[:name].any? # => false
+>> Person.create.errors[:name].any? # => true
+```
+
+We'll cover validation errors in greater depth in the [Working with Validation
+Errors](#working-with-validation-errors) section. For now, let's turn to the
+built-in validation helpers that Rails provides by default.
+
+Validation Helpers
+------------------
+
+Active Record offers many pre-defined validation helpers that you can use
+directly inside your class definitions. These helpers provide common validation
+rules. Every time a validation fails, an error message is added to the object's
+`errors` collection, and this message is associated with the attribute being
+validated.
+
+Each helper accepts an arbitrary number of attribute names, so with a single
+line of code you can add the same kind of validation to several attributes.
+
+All of them accept the `:on` and `:message` options, which define when the
+validation should be run and what message should be added to the `errors`
+collection if it fails, respectively. The `:on` option takes one of the values
+`:save` (the default), `:create` or `:update`. There is a default error
+message for each one of the validation helpers. These messages are used when
+the `:message` option isn't specified. Let's take a look at each one of the
+available helpers.
+
+### `acceptance`
+
+This method validates that a checkbox on the user interface was checked when a
+form was submitted. This is typically used when the user needs to agree to your
+application's terms of service, confirm reading some text, or any similar
+concept. This validation is very specific to web applications and this
+'acceptance' does not need to be recorded anywhere in your database (if you
+don't have a field for it, the helper will just create a virtual attribute).
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :terms_of_service, acceptance: true
+end
+```
+
+The default error message for this helper is _"must be accepted"_.
+
+It can receive an `:accept` option, which determines the value that will be
+considered acceptance. It defaults to "1" and can be easily changed.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :terms_of_service, acceptance: { accept: 'yes' }
+end
+```
+
+### `validates_associated`
+
+You should use this helper when your model has associations with other models
+and they also need to be validated. When you try to save your object, `valid?`
+will be called upon each one of the associated objects.
+
+```ruby
+class Library < ActiveRecord::Base
+ has_many :books
+ validates_associated :books
+end
+```
+
+This validation will work with all of the association types.
+
+CAUTION: Don't use `validates_associated` on both ends of your associations.
+They would call each other in an infinite loop.
+
+The default error message for `validates_associated` is _"is invalid"_. Note
+that each associated object will contain its own `errors` collection; errors do
+not bubble up to the calling model.
+
+### `confirmation`
+
+You should use this helper when you have two text fields that should receive
+exactly the same content. For example, you may want to confirm an email address
+or a password. This validation creates a virtual attribute whose name is the
+name of the field that has to be confirmed with "_confirmation" appended.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :email, confirmation: true
+end
+```
+
+In your view template you could use something like
+
+```erb
+<%= text_field :person, :email %>
+<%= text_field :person, :email_confirmation %>
+```
+
+This check is performed only if `email_confirmation` is not `nil`. To require
+confirmation, make sure to add a presence check for the confirmation attribute
+(we'll take a look at `presence` later on this guide):
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :email, confirmation: true
+ validates :email_confirmation, presence: true
+end
+```
+
+The default error message for this helper is _"doesn't match confirmation"_.
+
+### `exclusion`
+
+This helper validates that the attributes' values are not included in a given
+set. In fact, this set can be any enumerable object.
+
+```ruby
+class Account < ActiveRecord::Base
+ validates :subdomain, exclusion: { in: %w(www us ca jp),
+ message: "Subdomain %{value} is reserved." }
+end
+```
+
+The `exclusion` helper has an option `:in` that receives the set of values that
+will not be accepted for the validated attributes. The `:in` option has an
+alias called `:within` that you can use for the same purpose, if you'd like to.
+This example uses the `:message` option to show how you can include the
+attribute's value.
+
+The default error message is _"is reserved"_.
+
+### `format`
+
+This helper validates the attributes' values by testing whether they match a
+given regular expression, which is specified using the `:with` option.
+
+```ruby
+class Product < ActiveRecord::Base
+ validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
+ message: "Only letters allowed" }
+end
+```
+
+The default error message is _"is invalid"_.
+
+### `inclusion`
+
+This helper validates that the attributes' values are included in a given set.
+In fact, this set can be any enumerable object.
+
+```ruby
+class Coffee < ActiveRecord::Base
+ validates :size, inclusion: { in: %w(small medium large),
+ message: "%{value} is not a valid size" }
+end
+```
+
+The `inclusion` helper has an option `:in` that receives the set of values that
+will be accepted. The `:in` option has an alias called `:within` that you can
+use for the same purpose, if you'd like to. The previous example uses the
+`:message` option to show how you can include the attribute's value.
+
+The default error message for this helper is _"is not included in the list"_.
+
+### `length`
+
+This helper validates the length of the attributes' values. It provides a
+variety of options, so you can specify length constraints in different ways:
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, length: { minimum: 2 }
+ validates :bio, length: { maximum: 500 }
+ validates :password, length: { in: 6..20 }
+ validates :registration_number, length: { is: 6 }
+end
+```
+
+The possible length constraint options are:
+
+* `:minimum` - The attribute cannot have less than the specified length.
+* `:maximum` - The attribute cannot have more than the specified length.
+* `:in` (or `:within`) - The attribute length must be included in a given
+ interval. The value for this option must be a range.
+* `:is` - The attribute length must be equal to the given value.
+
+The default error messages depend on the type of length validation being
+performed. You can personalize these messages using the `:wrong_length`,
+`:too_long`, and `:too_short` options and `%{count}` as a placeholder for the
+number corresponding to the length constraint being used. You can still use the
+`:message` option to specify an error message.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :bio, length: { maximum: 1000,
+ too_long: "%{count} characters is the maximum allowed" }
+end
+```
+
+This helper counts characters by default, but you can split the value in a
+different way using the `:tokenizer` option:
+
+```ruby
+class Essay < ActiveRecord::Base
+ validates :content, length: {
+ minimum: 300,
+ maximum: 400,
+ tokenizer: lambda { |str| str.scan(/\w+/) },
+ too_short: "must have at least %{count} words",
+ too_long: "must have at most %{count} words"
+ }
+end
+```
+
+Note that the default error messages are plural (e.g., "is too short (minimum
+is %{count} characters)"). For this reason, when `:minimum` is 1 you should
+provide a personalized message or use `validates_presence_of` instead. When
+`:in` or `:within` have a lower limit of 1, you should either provide a
+personalized message or call `presence` prior to `length`.
+
+The `size` helper is an alias for `length`.
+
+### `numericality`
+
+This helper validates that your attributes have only numeric values. By
+default, it will match an optional sign followed by an integral or floating
+point number. To specify that only integral numbers are allowed set
+`:only_integer` to true.
+
+If you set `:only_integer` to `true`, then it will use the
+
+```ruby
+/\A[+-]?\d+\Z/
+```
+
+regular expression to validate the attribute's value. Otherwise, it will try to
+convert the value to a number using `Float`.
+
+WARNING. Note that the regular expression above allows a trailing newline
+character.
+
+```ruby
+class Player < ActiveRecord::Base
+ validates :points, numericality: true
+ validates :games_played, numericality: { only_integer: true }
+end
+```
+
+Besides `:only_integer`, this helper also accepts the following options to add
+constraints to acceptable values:
+
+* `:greater_than` - Specifies the value must be greater than the supplied
+ value. The default error message for this option is _"must be greater than
+ %{count}"_.
+* `:greater_than_or_equal_to` - Specifies the value must be greater than or
+ equal to the supplied value. The default error message for this option is
+ _"must be greater than or equal to %{count}"_.
+* `:equal_to` - Specifies the value must be equal to the supplied value. The
+ default error message for this option is _"must be equal to %{count}"_.
+* `:less_than` - Specifies the value must be less than the supplied value. The
+ default error message for this option is _"must be less than %{count}"_.
+* `:less_than_or_equal_to` - Specifies the value must be less than or equal the
+ supplied value. The default error message for this option is _"must be less
+ than or equal to %{count}"_.
+* `:odd` - Specifies the value must be an odd number if set to true. The
+ default error message for this option is _"must be odd"_.
+* `:even` - Specifies the value must be an even number if set to true. The
+ default error message for this option is _"must be even"_.
+
+The default error message is _"is not a number"_.
+
+### `presence`
+
+This helper validates that the specified attributes are not empty. It uses the
+`blank?` method to check if the value is either `nil` or a blank string, that
+is, a string that is either empty or consists of whitespace.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, :login, :email, presence: true
+end
+```
+
+If you want to be sure that an association is present, you'll need to test
+whether the associated object itself is present, and not the foreign key used
+to map the association.
+
+```ruby
+class LineItem < ActiveRecord::Base
+ belongs_to :order
+ validates :order, presence: true
+end
+```
+
+In order to validate associated records whose presence is required, you must
+specify the `:inverse_of` option for the association:
+
+```ruby
+class Order < ActiveRecord::Base
+ has_many :line_items, inverse_of: :order
+end
+```
+
+If you validate the presence of an object associated via a `has_one` or
+`has_many` relationship, it will check that the object is neither `blank?` nor
+`marked_for_destruction?`.
+
+Since `false.blank?` is true, if you want to validate the presence of a boolean
+field you should use `validates :field_name, inclusion: { in: [true, false] }`.
+
+The default error message is _"can't be empty"_.
+
+### `uniqueness`
+
+This helper validates that the attribute's value is unique right before the
+object gets saved. It does not create a uniqueness constraint in the database,
+so it may happen that two different database connections create two records
+with the same value for a column that you intend to be unique. To avoid that,
+you must create a unique index in your database.
+
+```ruby
+class Account < ActiveRecord::Base
+ validates :email, uniqueness: true
+end
+```
+
+The validation happens by performing an SQL query into the model's table,
+searching for an existing record with the same value in that attribute.
+
+There is a `:scope` option that you can use to specify other attributes that
+are used to limit the uniqueness check:
+
+```ruby
+class Holiday < ActiveRecord::Base
+ validates :name, uniqueness: { scope: :year,
+ message: "should happen once per year" }
+end
+```
+
+There is also a `:case_sensitive` option that you can use to define whether the
+uniqueness constraint will be case sensitive or not. This option defaults to
+true.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, uniqueness: { case_sensitive: false }
+end
+```
+
+WARNING. Note that some databases are configured to perform case-insensitive
+searches anyway.
+
+The default error message is _"has already been taken"_.
+
+### `validates_with`
+
+This helper passes the record to a separate class for validation.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates_with GoodnessValidator
+end
+
+class GoodnessValidator < ActiveModel::Validator
+ def validate(record)
+ if record.first_name == "Evil"
+ record.errors[:base] << "This person is evil"
+ end
+ end
+end
+```
+
+NOTE: Errors added to `record.errors[:base]` relate to the state of the record
+as a whole, and not to a specific attribute.
+
+The `validates_with` helper takes a class, or a list of classes to use for
+validation. There is no default error message for `validates_with`. You must
+manually add errors to the record's errors collection in the validator class.
+
+To implement the validate method, you must have a `record` parameter defined,
+which is the record to be validated.
+
+Like all other validations, `validates_with` takes the `:if`, `:unless` and
+`:on` options. If you pass any other options, it will send those options to the
+validator class as `options`:
+
+```ruby
+class Person < ActiveRecord::Base
+ validates_with GoodnessValidator, fields: [:first_name, :last_name]
+end
+
+class GoodnessValidator < ActiveModel::Validator
+ def validate(record)
+ if options[:fields].any?{|field| record.send(field) == "Evil" }
+ record.errors[:base] << "This person is evil"
+ end
+ end
+end
+```
+
+### `validates_each`
+
+This helper validates attributes against a block. It doesn't have a predefined
+validation function. You should create one using a block, and every attribute
+passed to `validates_each` will be tested against it. In the following example,
+we don't want names and surnames to begin with lower case.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates_each :name, :surname do |record, attr, value|
+ record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
+ end
+end
+```
+
+The block receives the record, the attribute's name and the attribute's value.
+You can do anything you like to check for valid data within the block. If your
+validation fails, you should add an error message to the model, therefore
+making it invalid.
+
+Common Validation Options
+-------------------------
+
+These are common validation options:
+
+### `:allow_nil`
+
+The `:allow_nil` option skips the validation when the value being validated is
+`nil`.
+
+```ruby
+class Coffee < ActiveRecord::Base
+ validates :size, inclusion: { in: %w(small medium large),
+ message: "%{value} is not a valid size" }, allow_nil: true
+end
+```
+
+### `:allow_blank`
+
+The `:allow_blank` option is similar to the `:allow_nil` option. This option
+will let validation pass if the attribute's value is `blank?`, like `nil` or an
+empty string for example.
+
+```ruby
+class Topic < ActiveRecord::Base
+ validates :title, length: { is: 5 }, allow_blank: true
+end
+
+Topic.create("title" => "").valid? # => true
+Topic.create("title" => nil).valid? # => true
+```
+
+### `:message`
+
+As you've already seen, the `:message` option lets you specify the message that
+will be added to the `errors` collection when validation fails. When this
+option is not used, Active Record will use the respective default error message
+for each validation helper.
+
+### `:on`
+
+The `:on` option lets you specify when the validation should happen. The
+default behavior for all the built-in validation helpers is to be run on save
+(both when you're creating a new record and when you're updating it). If you
+want to change it, you can use `on: :create` to run the validation only when a
+new record is created or `on: :update` to run the validation only when a record
+is updated.
+
+```ruby
+class Person < ActiveRecord::Base
+ # it will be possible to update email with a duplicated value
+ validates :email, uniqueness: true, on: :create
+
+ # it will be possible to create the record with a non-numerical age
+ validates :age, numericality: true, on: :update
+
+ # the default (validates on both create and update)
+ validates :name, presence: true, on: :save
+end
+```
+
+Strict Validations
+------------------
+
+You can also specify validations to be strict and raise
+`ActiveModel::StrictValidationFailed` when the object is invalid.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: { strict: true }
+end
+
+Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank
+```
+
+There is also an ability to pass custom exception to `:strict` option
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
+end
+
+Person.new.valid? # => TokenGenerationException: Token can't be blank
+```
+
+Conditional Validation
+----------------------
+
+Sometimes it will make sense to validate an object only when a given predicate
+is satisfied. You can do that by using the `:if` and `:unless` options, which
+can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if`
+option when you want to specify when the validation **should** happen. If you
+want to specify when the validation **should not** happen, then you may use the
+`:unless` option.
+
+### Using a Symbol with `:if` and `:unless`
+
+You can associate the `:if` and `:unless` options with a symbol corresponding
+to the name of a method that will get called right before validation happens.
+This is the most commonly used option.
+
+```ruby
+class Order < ActiveRecord::Base
+ validates :card_number, presence: true, if: :paid_with_card?
+
+ def paid_with_card?
+ payment_type == "card"
+ end
+end
+```
+
+### Using a String with `:if` and `:unless`
+
+You can also use a string that will be evaluated using `eval` and needs to
+contain valid Ruby code. You should use this option only when the string
+represents a really short condition.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :surname, presence: true, if: "name.nil?"
+end
+```
+
+### Using a Proc with `:if` and `:unless`
+
+Finally, it's possible to associate `:if` and `:unless` with a `Proc` object
+which will be called. Using a `Proc` object gives you the ability to write an
+inline condition instead of a separate method. This option is best suited for
+one-liners.
+
+```ruby
+class Account < ActiveRecord::Base
+ validates :password, confirmation: true,
+ unless: Proc.new { |a| a.password.blank? }
+end
+```
+
+### Grouping Conditional validations
+
+Sometimes it is useful to have multiple validations use one condition, it can
+be easily achieved using `with_options`.
+
+```ruby
+class User < ActiveRecord::Base
+ with_options if: :is_admin? do |admin|
+ admin.validates :password, length: { minimum: 10 }
+ admin.validates :email, presence: true
+ end
+end
+```
+
+All validations inside of `with_options` block will have automatically passed
+the condition `if: :is_admin?`
+
+### Combining Validation Conditions
+
+On the other hand, when multiple conditions define whether or not a validation
+should happen, an `Array` can be used. Moreover, you can apply both `:if` and
+`:unless` to the same validation.
+
+```ruby
+class Computer < ActiveRecord::Base
+ validates :mouse, presence: true,
+ if: ["market.retail?", :desktop?]
+ unless: Proc.new { |c| c.trackpad.present? }
+end
+```
+
+The validation only runs when all the `:if` conditions and none of the
+`:unless` conditions are evaluated to `true`.
+
+Performing Custom Validations
+-----------------------------
+
+When the built-in validation helpers are not enough for your needs, you can
+write your own validators or validation methods as you prefer.
+
+### Custom Validators
+
+Custom validators are classes that extend `ActiveModel::Validator`. These
+classes must implement a `validate` method which takes a record as an argument
+and performs the validation on it. The custom validator is called using the
+`validates_with` method.
+
+```ruby
+class MyValidator < ActiveModel::Validator
+ def validate(record)
+ unless record.name.starts_with? 'X'
+ record.errors[:name] << 'Need a name starting with X please!'
+ end
+ end
+end
+
+class Person
+ include ActiveModel::Validations
+ validates_with MyValidator
+end
+```
+
+The easiest way to add custom validators for validating individual attributes
+is with the convenient `ActiveModel::EachValidator`. In this case, the custom
+validator class must implement a `validate_each` method which takes three
+arguments: record, attribute and value which correspond to the instance, the
+attribute to be validated and the value of the attribute in the passed
+instance.
+
+```ruby
+class EmailValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
+ record.errors[attribute] << (options[:message] || "is not an email")
+ end
+ end
+end
+
+class Person < ActiveRecord::Base
+ validates :email, presence: true, email: true
+end
+```
+
+As shown in the example, you can also combine standard validations with your
+own custom validators.
+
+### Custom Methods
+
+You can also create methods that verify the state of your models and add
+messages to the `errors` collection when they are invalid. You must then
+register these methods by using the `validate` class method, passing in the
+symbols for the validation methods' names.
+
+You can pass more than one symbol for each class method and the respective
+validations will be run in the same order as they were registered.
+
+```ruby
+class Invoice < ActiveRecord::Base
+ validate :expiration_date_cannot_be_in_the_past,
+ :discount_cannot_be_greater_than_total_value
+
+ def expiration_date_cannot_be_in_the_past
+ if expiration_date.present? && expiration_date < Date.today
+ errors.add(:expiration_date, "can't be in the past")
+ end
+ end
+
+ def discount_cannot_be_greater_than_total_value
+ if discount > total_value
+ errors.add(:discount, "can't be greater than total value")
+ end
+ end
+end
+```
+
+By default such validations will run every time you call `valid?`. It is also
+possible to control when to run these custom validations by giving an `:on`
+option to the `validate` method, with either: `:create` or `:update`.
+
+```ruby
+class Invoice < ActiveRecord::Base
+ validate :active_customer, on: :create
+
+ def active_customer
+ errors.add(:customer_id, "is not active") unless customer.active?
+ end
+end
+```
+
+Working with Validation Errors
+------------------------------
+
+In addition to the `valid?` and `invalid?` methods covered earlier, Rails provides a number of methods for working with the `errors` collection and inquiring about the validity of objects.
+
+The following is a list of the most commonly used methods. Please refer to the `ActiveModel::Errors` documentation for a list of all the available methods.
+
+### `errors`
+
+Returns an instance of the class `ActiveModel::Errors` containing all errors. Each key is the attribute name and the value is an array of strings with all errors.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true, length: { minimum: 3 }
+end
+
+person = Person.new
+person.valid? # => false
+person.errors
+ # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
+
+person = Person.new(name: "John Doe")
+person.valid? # => true
+person.errors # => []
+```
+
+### `errors[]`
+
+`errors[]` is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true, length: { minimum: 3 }
+end
+
+person = Person.new(name: "John Doe")
+person.valid? # => true
+person.errors[:name] # => []
+
+person = Person.new(name: "JD")
+person.valid? # => false
+person.errors[:name] # => ["is too short (minimum is 3 characters)"]
+
+person = Person.new
+person.valid? # => false
+person.errors[:name]
+ # => ["can't be blank", "is too short (minimum is 3 characters)"]
+```
+
+### `errors.add`
+
+The `add` method lets you manually add messages that are related to particular attributes. You can use the `errors.full_messages` or `errors.to_a` methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). `add` receives the name of the attribute you want to add the message to, and the message itself.
+
+```ruby
+class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors.add(:name, "cannot contain the characters !@#%*()_-+=")
+ end
+end
+
+person = Person.create(name: "!@#")
+
+person.errors[:name]
+ # => ["cannot contain the characters !@#%*()_-+="]
+
+person.errors.full_messages
+ # => ["Name cannot contain the characters !@#%*()_-+="]
+```
+
+Another way to do this is using `[]=` setter
+
+```ruby
+ class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors[:name] = "cannot contain the characters !@#%*()_-+="
+ end
+ end
+
+ person = Person.create(name: "!@#")
+
+ person.errors[:name]
+ # => ["cannot contain the characters !@#%*()_-+="]
+
+ person.errors.to_a
+ # => ["Name cannot contain the characters !@#%*()_-+="]
+```
+
+### `errors[:base]`
+
+You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message.
+
+```ruby
+class Person < ActiveRecord::Base
+ def a_method_used_for_validation_purposes
+ errors[:base] << "This person is invalid because ..."
+ end
+end
+```
+
+### `errors.clear`
+
+The `clear` method is used when you intentionally want to clear all the messages in the `errors` collection. Of course, calling `errors.clear` upon an invalid object won't actually make it valid: the `errors` collection will now be empty, but the next time you call `valid?` or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the `errors` collection will be filled again.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true, length: { minimum: 3 }
+end
+
+person = Person.new
+person.valid? # => false
+person.errors[:name]
+ # => ["can't be blank", "is too short (minimum is 3 characters)"]
+
+person.errors.clear
+person.errors.empty? # => true
+
+p.save # => false
+
+p.errors[:name]
+# => ["can't be blank", "is too short (minimum is 3 characters)"]
+```
+
+### `errors.size`
+
+The `size` method returns the total number of error messages for the object.
+
+```ruby
+class Person < ActiveRecord::Base
+ validates :name, presence: true, length: { minimum: 3 }
+end
+
+person = Person.new
+person.valid? # => false
+person.errors.size # => 2
+
+person = Person.new(name: "Andrea", email: "andrea@example.com")
+person.valid? # => true
+person.errors.size # => 0
+```
+
+Displaying Validation Errors in Views
+-------------------------------------
+
+Once you've created a model and added validations, if that model is created via
+a web form, you probably want to display an error message when one of the
+validations fail.
+
+Because every application handles this kind of thing differently, Rails does
+not include any view helpers to help you generate these messages directly.
+However, due to the rich number of methods Rails gives you to interact with
+validations in general, it's fairly easy to build your own. In addition, when
+generating a scaffold, Rails will put some ERB into the `_form.html.erb` that
+it generates that displays the full list of errors on that model.
+
+Assuming we have a model that's been saved in an instance variable named
+`@post`, it looks like this:
+
+```ruby
+<% if @post.errors.any? %>
+ <div id="error_explanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
+
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
+<% end %>
+```
+
+Furthermore, if you use the Rails form helpers to generate your forms, when
+a validation error occurs on a field, it will generate an extra `<div>` around
+the entry.
+
+```
+<div class="field_with_errors">
+ <input id="post_title" name="post[title]" size="30" type="text" value="">
+</div>
+```
+
+You can then style this div however you'd like. The default scaffold that
+Rails generates, for example, adds this CSS rule:
+
+```
+.field_with_errors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+}
+```
+
+This means that any field with an error ends up with a 2 pixel red border.
diff --git a/guides/source/active_record_validations_callbacks.md b/guides/source/active_record_validations_callbacks.md
deleted file mode 100644
index 5c27ccbf9e..0000000000
--- a/guides/source/active_record_validations_callbacks.md
+++ /dev/null
@@ -1,1368 +0,0 @@
-Active Record Validations and Callbacks
-=======================================
-
-This guide teaches you how to hook into the life cycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle.
-
-After reading this guide and trying out the presented concepts, we hope that you'll be able to:
-
-* Understand the life cycle of Active Record objects
-* Use the built-in Active Record validation helpers
-* Create your own custom validation methods
-* Work with the error messages generated by the validation process
-* Create callback methods that respond to events in the object life cycle
-* Create special classes that encapsulate common behavior for your callbacks
-* Create Observers that respond to life cycle events outside of the original class
-
---------------------------------------------------------------------------------
-
-The Object Life Cycle
----------------------
-
-During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object life cycle</em> so that you can control your application and its data.
-
-Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state.
-
-Validations Overview
---------------------
-
-Before you dive into the detail of validations in Rails, you should understand a bit about how validations fit into the big picture.
-
-### Why Use Validations?
-
-Validations are used to ensure that only valid data is saved into your database. For example, it may be important to your application to ensure that every user provides a valid email address and mailing address.
-
-There are several ways to validate data before it is saved into your database, including native database constraints, client-side validations, controller-level validations, and model-level validations:
-
-* Database constraints and/or stored procedures make the validation mechanisms database-dependent and can make testing and maintenance more difficult. However, if your database is used by other applications, it may be a good idea to use some constraints at the database level. Additionally, database-level validations can safely handle some things (such as uniqueness in heavily-used tables) that can be difficult to implement otherwise.
-* Client-side validations can be useful, but are generally unreliable if used alone. If they are implemented using JavaScript, they may be bypassed if JavaScript is turned off in the user's browser. However, if combined with other techniques, client-side validation can be a convenient way to provide users with immediate feedback as they use your site.
-* Controller-level validations can be tempting to use, but often become unwieldy and difficult to test and maintain. Whenever possible, it's a good idea to [keep your controllers skinny](http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model), as it will make your application a pleasure to work with in the long run.
-* Model-level validations are the best way to ensure that only valid data is saved into your database. They are database agnostic, cannot be bypassed by end users, and are convenient to test and maintain. Rails makes them easy to use, provides built-in helpers for common needs, and allows you to create your own validation methods as well.
-
-### When Does Validation Happen?
-
-There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, for example using the `new` method, that object does not belong to the database yet. Once you call `save` upon that object it will be saved into the appropriate database table. Active Record uses the `new_record?` instance method to determine whether an object is already in the database or not. Consider the following simple Active Record class:
-
-```ruby
-class Person < ActiveRecord::Base
-end
-```
-
-We can see how it works by looking at some `rails console` output:
-
-```ruby
-$ rails console
->> p = Person.new(name: "John Doe")
-=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>
->> p.new_record?
-=> true
->> p.save
-=> true
->> p.new_record?
-=> false
-```
-
-TIP: All lines starting with a dollar sign `$` are intended to be run on the command line.
-
-Creating and saving a new record will send an SQL `INSERT` operation to the database. Updating an existing record will send an SQL `UPDATE` operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the `INSERT` or `UPDATE` operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.
-
-CAUTION: There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful.
-
-The following methods trigger validations, and will save the object to the database only if the object is valid:
-
-* `create`
-* `create!`
-* `save`
-* `save!`
-* `update`
-* `update_attributes`
-* `update_attributes!`
-
-The bang versions (e.g. `save!`) raise an exception if the record is invalid. The non-bang versions don't: `save` and `update_attributes` return `false`, `create` and `update` just return the objects.
-
-### Skipping Validations
-
-The following methods skip validations, and will save the object to the database regardless of its validity. They should be used with caution.
-
-* `decrement!`
-* `decrement_counter`
-* `increment!`
-* `increment_counter`
-* `toggle!`
-* `touch`
-* `update_all`
-* `update_attribute`
-* `update_column`
-* `update_columns`
-* `update_counters`
-
-Note that `save` also has the ability to skip validations if passed `validate: false` as argument. This technique should be used with caution.
-
-* `save(validate: false)`
-
-### `valid?` and `invalid?`
-
-To verify whether or not an object is valid, Rails uses the `valid?` method. You can also use this method on your own. `valid?` triggers your validations and returns true if no errors were found in the object, and false otherwise.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: true
-end
-
-Person.create(name: "John Doe").valid? # => true
-Person.create(name: nil).valid? # => false
-```
-
-After Active Record has performed validations, any errors found can be accessed through the `errors` instance method, which returns a collection of errors. By definition, an object is valid if this collection is empty after running validations.
-
-Note that an object instantiated with `new` will not report errors even if it's technically invalid, because validations are not run when using `new`.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: true
-end
-
->> p = Person.new
-#=> #<Person id: nil, name: nil>
->> p.errors
-#=> {}
-
->> p.valid?
-#=> false
->> p.errors
-#=> {name:["can't be blank"]}
-
->> p = Person.create
-#=> #<Person id: nil, name: nil>
->> p.errors
-#=> {name:["can't be blank"]}
-
->> p.save
-#=> false
-
->> p.save!
-#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
-
->> Person.create!
-#=> ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
-```
-
-`invalid?` is simply the inverse of `valid?`. It triggers your validations, returning true if any errors were found in the object, and false otherwise.
-
-### `errors[]`
-
-To verify whether or not a particular attribute of an object is valid, you can use `errors[:attribute]`. It returns an array of all the errors for `:attribute`. If there are no errors on the specified attribute, an empty array is returned.
-
-This method is only useful _after_ validations have been run, because it only inspects the errors collection and does not trigger validations itself. It's different from the `ActiveRecord::Base#invalid?` method explained above because it doesn't verify the validity of the object as a whole. It only checks to see whether there are errors found on an individual attribute of the object.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: true
-end
-
->> Person.new.errors[:name].any? # => false
->> Person.create.errors[:name].any? # => true
-```
-
-We'll cover validation errors in greater depth in the [Working with Validation Errors](#working-with-validation-errors) section. For now, let's turn to the built-in validation helpers that Rails provides by default.
-
-Validation Helpers
-------------------
-
-Active Record offers many pre-defined validation helpers that you can use directly inside your class definitions. These helpers provide common validation rules. Every time a validation fails, an error message is added to the object's `errors` collection, and this message is associated with the attribute being validated.
-
-Each helper accepts an arbitrary number of attribute names, so with a single line of code you can add the same kind of validation to several attributes.
-
-All of them accept the `:on` and `:message` options, which define when the validation should be run and what message should be added to the `errors` collection if it fails, respectively. The `:on` option takes one of the values `:save` (the default), `:create` or `:update`. There is a default error message for each one of the validation helpers. These messages are used when the `:message` option isn't specified. Let's take a look at each one of the available helpers.
-
-### `acceptance`
-
-Validates that a checkbox on the user interface was checked when a form was submitted. This is typically used when the user needs to agree to your application's terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute).
-
-```ruby
-class Person < ActiveRecord::Base
- validates :terms_of_service, acceptance: true
-end
-```
-
-The default error message for this helper is "_must be accepted_".
-
-It can receive an `:accept` option, which determines the value that will be considered acceptance. It defaults to "1" and can be easily changed.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :terms_of_service, acceptance: { accept: 'yes' }
-end
-```
-
-### `validates_associated`
-
-You should use this helper when your model has associations with other models and they also need to be validated. When you try to save your object, `valid?` will be called upon each one of the associated objects.
-
-```ruby
-class Library < ActiveRecord::Base
- has_many :books
- validates_associated :books
-end
-```
-
-This validation will work with all of the association types.
-
-CAUTION: Don't use `validates_associated` on both ends of your associations. They would call each other in an infinite loop.
-
-The default error message for `validates_associated` is "_is invalid_". Note that each associated object will contain its own `errors` collection; errors do not bubble up to the calling model.
-
-### `confirmation`
-
-You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute whose name is the name of the field that has to be confirmed with "_confirmation" appended.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :email, confirmation: true
-end
-```
-
-In your view template you could use something like
-
-```erb
-<%= text_field :person, :email %>
-<%= text_field :person, :email_confirmation %>
-```
-
-This check is performed only if `email_confirmation` is not `nil`. To require confirmation, make sure to add a presence check for the confirmation attribute (we'll take a look at `presence` later on this guide):
-
-```ruby
-class Person < ActiveRecord::Base
- validates :email, confirmation: true
- validates :email_confirmation, presence: true
-end
-```
-
-The default error message for this helper is "_doesn't match confirmation_".
-
-### `exclusion`
-
-This helper validates that the attributes' values are not included in a given set. In fact, this set can be any enumerable object.
-
-```ruby
-class Account < ActiveRecord::Base
- validates :subdomain, exclusion: { in: %w(www us ca jp),
- message: "Subdomain %{value} is reserved." }
-end
-```
-
-The `exclusion` helper has an option `:in` that receives the set of values that will not be accepted for the validated attributes. The `:in` option has an alias called `:within` that you can use for the same purpose, if you'd like to. This example uses the `:message` option to show how you can include the attribute's value.
-
-The default error message is "_is reserved_".
-
-### `format`
-
-This helper validates the attributes' values by testing whether they match a given regular expression, which is specified using the `:with` option.
-
-```ruby
-class Product < ActiveRecord::Base
- validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
- message: "Only letters allowed" }
-end
-```
-
-The default error message is "_is invalid_".
-
-### `inclusion`
-
-This helper validates that the attributes' values are included in a given set. In fact, this set can be any enumerable object.
-
-```ruby
-class Coffee < ActiveRecord::Base
- validates :size, inclusion: { in: %w(small medium large),
- message: "%{value} is not a valid size" }
-end
-```
-
-The `inclusion` helper has an option `:in` that receives the set of values that will be accepted. The `:in` option has an alias called `:within` that you can use for the same purpose, if you'd like to. The previous example uses the `:message` option to show how you can include the attribute's value.
-
-The default error message for this helper is "_is not included in the list_".
-
-### `length`
-
-This helper validates the length of the attributes' values. It provides a variety of options, so you can specify length constraints in different ways:
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, length: { minimum: 2 }
- validates :bio, length: { maximum: 500 }
- validates :password, length: { in: 6..20 }
- validates :registration_number, length: { is: 6 }
-end
-```
-
-The possible length constraint options are:
-
-* `:minimum` - The attribute cannot have less than the specified length.
-* `:maximum` - The attribute cannot have more than the specified length.
-* `:in` (or `:within`) - The attribute length must be included in a given interval. The value for this option must be a range.
-* `:is` - The attribute length must be equal to the given value.
-
-The default error messages depend on the type of length validation being performed. You can personalize these messages using the `:wrong_length`, `:too_long`, and `:too_short` options and `%{count}` as a placeholder for the number corresponding to the length constraint being used. You can still use the `:message` option to specify an error message.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :bio, length: { maximum: 1000,
- too_long: "%{count} characters is the maximum allowed" }
-end
-```
-
-This helper counts characters by default, but you can split the value in a different way using the `:tokenizer` option:
-
-```ruby
-class Essay < ActiveRecord::Base
- validates :content, length: {
- minimum: 300,
- maximum: 400,
- tokenizer: lambda { |str| str.scan(/\w+/) },
- too_short: "must have at least %{count} words",
- too_long: "must have at most %{count} words"
- }
-end
-```
-
-Note that the default error messages are plural (e.g., "is too short (minimum is %{count} characters)"). For this reason, when `:minimum` is 1 you should provide a personalized message or use `validates_presence_of` instead. When `:in` or `:within` have a lower limit of 1, you should either provide a personalized message or call `presence` prior to `length`.
-
-The `size` helper is an alias for `length`.
-
-### `numericality`
-
-This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by an integral or floating point number. To specify that only integral numbers are allowed set `:only_integer` to true.
-
-If you set `:only_integer` to `true`, then it will use the
-
-```ruby
-/\A[+-]?\d+\Z/
-```
-
-regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using `Float`.
-
-WARNING. Note that the regular expression above allows a trailing newline character.
-
-```ruby
-class Player < ActiveRecord::Base
- validates :points, numericality: true
- validates :games_played, numericality: { only_integer: true }
-end
-```
-
-Besides `:only_integer`, this helper also accepts the following options to add constraints to acceptable values:
-
-* `:greater_than` - Specifies the value must be greater than the supplied value. The default error message for this option is "_must be greater than %{count}_".
-* `:greater_than_or_equal_to` - Specifies the value must be greater than or equal to the supplied value. The default error message for this option is "_must be greater than or equal to %{count}_".
-* `:equal_to` - Specifies the value must be equal to the supplied value. The default error message for this option is "_must be equal to %{count}_".
-* `:less_than` - Specifies the value must be less than the supplied value. The default error message for this option is "_must be less than %{count}_".
-* `:less_than_or_equal_to` - Specifies the value must be less than or equal the supplied value. The default error message for this option is "_must be less than or equal to %{count}_".
-* `:odd` - Specifies the value must be an odd number if set to true. The default error message for this option is "_must be odd_".
-* `:even` - Specifies the value must be an even number if set to true. The default error message for this option is "_must be even_".
-
-The default error message is "_is not a number_".
-
-### `presence`
-
-This helper validates that the specified attributes are not empty. It uses the `blank?` method to check if the value is either `nil` or a blank string, that is, a string that is either empty or consists of whitespace.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, :login, :email, presence: true
-end
-```
-
-If you want to be sure that an association is present, you'll need to test whether the foreign key used to map the association is present, and not the associated object itself.
-
-```ruby
-class LineItem < ActiveRecord::Base
- belongs_to :order
- validates :order_id, presence: true
-end
-```
-
-If you validate the presence of an object associated via a `has_one` or `has_many` relationship, it will check that the object is neither `blank?` nor `marked_for_destruction?`.
-
-Since `false.blank?` is true, if you want to validate the presence of a boolean field you should use `validates :field_name, inclusion: { in: [true, false] }`.
-
-The default error message is "_can't be empty_".
-
-### `uniqueness`
-
-This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index in your database.
-
-```ruby
-class Account < ActiveRecord::Base
- validates :email, uniqueness: true
-end
-```
-
-The validation happens by performing an SQL query into the model's table, searching for an existing record with the same value in that attribute.
-
-There is a `:scope` option that you can use to specify other attributes that are used to limit the uniqueness check:
-
-```ruby
-class Holiday < ActiveRecord::Base
- validates :name, uniqueness: { scope: :year,
- message: "should happen once per year" }
-end
-```
-
-There is also a `:case_sensitive` option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, uniqueness: { case_sensitive: false }
-end
-```
-
-WARNING. Note that some databases are configured to perform case-insensitive searches anyway.
-
-The default error message is "_has already been taken_".
-
-### `validates_with`
-
-This helper passes the record to a separate class for validation.
-
-```ruby
-class Person < ActiveRecord::Base
- validates_with GoodnessValidator
-end
-
-class GoodnessValidator < ActiveModel::Validator
- def validate(record)
- if record.first_name == "Evil"
- record.errors[:base] << "This person is evil"
- end
- end
-end
-```
-
-NOTE: Errors added to `record.errors[:base]` relate to the state of the record as a whole, and not to a specific attribute.
-
-The `validates_with` helper takes a class, or a list of classes to use for validation. There is no default error message for `validates_with`. You must manually add errors to the record's errors collection in the validator class.
-
-To implement the validate method, you must have a `record` parameter defined, which is the record to be validated.
-
-Like all other validations, `validates_with` takes the `:if`, `:unless` and `:on` options. If you pass any other options, it will send those options to the validator class as `options`:
-
-```ruby
-class Person < ActiveRecord::Base
- validates_with GoodnessValidator, fields: [:first_name, :last_name]
-end
-
-class GoodnessValidator < ActiveModel::Validator
- def validate(record)
- if options[:fields].any?{|field| record.send(field) == "Evil" }
- record.errors[:base] << "This person is evil"
- end
- end
-end
-```
-
-### `validates_each`
-
-This helper validates attributes against a block. It doesn't have a predefined validation function. You should create one using a block, and every attribute passed to `validates_each` will be tested against it. In the following example, we don't want names and surnames to begin with lower case.
-
-```ruby
-class Person < ActiveRecord::Base
- validates_each :name, :surname do |record, attr, value|
- record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
- end
-end
-```
-
-The block receives the record, the attribute's name and the attribute's value. You can do anything you like to check for valid data within the block. If your validation fails, you should add an error message to the model, therefore making it invalid.
-
-Common Validation Options
--------------------------
-
-These are common validation options:
-
-### `:allow_nil`
-
-The `:allow_nil` option skips the validation when the value being validated is `nil`.
-
-```ruby
-class Coffee < ActiveRecord::Base
- validates :size, inclusion: { in: %w(small medium large),
- message: "%{value} is not a valid size" }, allow_nil: true
-end
-```
-
-TIP: `:allow_nil` is ignored by the presence validator.
-
-### `:allow_blank`
-
-The `:allow_blank` option is similar to the `:allow_nil` option. This option will let validation pass if the attribute's value is `blank?`, like `nil` or an empty string for example.
-
-```ruby
-class Topic < ActiveRecord::Base
- validates :title, length: { is: 5 }, allow_blank: true
-end
-
-Topic.create("title" => "").valid? # => true
-Topic.create("title" => nil).valid? # => true
-```
-
-TIP: `:allow_blank` is ignored by the presence validator.
-
-### `:message`
-
-As you've already seen, the `:message` option lets you specify the message that will be added to the `errors` collection when validation fails. When this option is not used, Active Record will use the respective default error message for each validation helper.
-
-### `:on`
-
-The `:on` option lets you specify when the validation should happen. The default behavior for all the built-in validation helpers is to be run on save (both when you're creating a new record and when you're updating it). If you want to change it, you can use `on: :create` to run the validation only when a new record is created or `on: :update` to run the validation only when a record is updated.
-
-```ruby
-class Person < ActiveRecord::Base
- # it will be possible to update email with a duplicated value
- validates :email, uniqueness: true, on: :create
-
- # it will be possible to create the record with a non-numerical age
- validates :age, numericality: true, on: :update
-
- # the default (validates on both create and update)
- validates :name, presence: true, on: :save
-end
-```
-
-Strict Validations
-------------------
-
-You can also specify validations to be strict and raise `ActiveModel::StrictValidationFailed` when the object is invalid.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: { strict: true }
-end
-
-Person.new.valid? #=> ActiveModel::StrictValidationFailed: Name can't be blank
-```
-
-There is also an ability to pass custom exception to `:strict` option
-
-```ruby
-class Person < ActiveRecord::Base
- validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
-end
-
-Person.new.valid? #=> TokenGenerationException: Token can't be blank
-```
-
-Conditional Validation
-----------------------
-
-Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the `:if` and `:unless` options, which can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if` option when you want to specify when the validation **should** happen. If you want to specify when the validation **should not** happen, then you may use the `:unless` option.
-
-### Using a Symbol with `:if` and `:unless`
-
-You can associate the `:if` and `:unless` options with a symbol corresponding to the name of a method that will get called right before validation happens. This is the most commonly used option.
-
-```ruby
-class Order < ActiveRecord::Base
- validates :card_number, presence: true, if: :paid_with_card?
-
- def paid_with_card?
- payment_type == "card"
- end
-end
-```
-
-### Using a String with `:if` and `:unless`
-
-You can also use a string that will be evaluated using `eval` and needs to contain valid Ruby code. You should use this option only when the string represents a really short condition.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :surname, presence: true, if: "name.nil?"
-end
-```
-
-### Using a Proc with `:if` and `:unless`
-
-Finally, it's possible to associate `:if` and `:unless` with a `Proc` object which will be called. Using a `Proc` object gives you the ability to write an inline condition instead of a separate method. This option is best suited for one-liners.
-
-```ruby
-class Account < ActiveRecord::Base
- validates :password, confirmation: true,
- unless: Proc.new { |a| a.password.blank? }
-end
-```
-
-### Grouping conditional validations
-
-Sometimes it is useful to have multiple validations use one condition, it can be easily achieved using `with_options`.
-
-```ruby
-class User < ActiveRecord::Base
- with_options if: :is_admin? do |admin|
- admin.validates :password, length: { minimum: 10 }
- admin.validates :email, presence: true
- end
-end
-```
-
-All validations inside of `with_options` block will have automatically passed the condition `if: :is_admin?`
-
-### Combining validation conditions
-
-On the other hand, when multiple conditions define whether or not a validation should happen, an `Array` can be used. Moreover, you can apply both `:if` and `:unless` to the same validation.
-
-```ruby
-class Computer < ActiveRecord::Base
- validates :mouse, presence: true,
- if: ["market.retail?", :desktop?]
- unless: Proc.new { |c| c.trackpad.present? }
-end
-```
-
-The validation only runs when all the `:if` conditions and none of the `:unless` conditions are evaluated to `true`.
-
-Performing Custom Validations
------------------------------
-
-When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods as you prefer.
-
-### Custom Validators
-
-Custom validators are classes that extend `ActiveModel::Validator`. These classes must implement a `validate` method which takes a record as an argument and performs the validation on it. The custom validator is called using the `validates_with` method.
-
-```ruby
-class MyValidator < ActiveModel::Validator
- def validate(record)
- unless record.name.starts_with? 'X'
- record.errors[:name] << 'Need a name starting with X please!'
- end
- end
-end
-
-class Person
- include ActiveModel::Validations
- validates_with MyValidator
-end
-```
-
-The easiest way to add custom validators for validating individual attributes is with the convenient `ActiveModel::EachValidator`. In this case, the custom validator class must implement a `validate_each` method which takes three arguments: record, attribute and value which correspond to the instance, the attribute to be validated and the value of the attribute in the passed instance.
-
-```ruby
-class EmailValidator < ActiveModel::EachValidator
- def validate_each(record, attribute, value)
- unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
- record.errors[attribute] << (options[:message] || "is not an email")
- end
- end
-end
-
-class Person < ActiveRecord::Base
- validates :email, presence: true, email: true
-end
-```
-
-As shown in the example, you can also combine standard validations with your own custom validators.
-
-### Custom Methods
-
-You can also create methods that verify the state of your models and add messages to the `errors` collection when they are invalid. You must then register these methods by using the `validate` class method, passing in the symbols for the validation methods' names.
-
-You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.
-
-```ruby
-class Invoice < ActiveRecord::Base
- validate :expiration_date_cannot_be_in_the_past,
- :discount_cannot_be_greater_than_total_value
-
- def expiration_date_cannot_be_in_the_past
- if !expiration_date.blank? and expiration_date < Date.today
- errors.add(:expiration_date, "can't be in the past")
- end
- end
-
- def discount_cannot_be_greater_than_total_value
- if discount > total_value
- errors.add(:discount, "can't be greater than total value")
- end
- end
-end
-```
-
-By default such validations will run every time you call `valid?`. It is also possible to control when to run these custom validations by giving an `:on` option to the `validate` method, with either: `:create` or `:update`.
-
-```ruby
-class Invoice < ActiveRecord::Base
- validate :active_customer, on: :create
-
- def active_customer
- errors.add(:customer_id, "is not active") unless customer.active?
- end
-end
-```
-
-You can even create your own validation helpers and reuse them in several different models. For example, an application that manages surveys may find it useful to express that a certain field corresponds to a set of choices:
-
-```ruby
-ActiveRecord::Base.class_eval do
- def self.validates_as_choice(attr_name, n, options={})
- validates attr_name, inclusion: { { in: 1..n }.merge!(options) }
- end
-end
-```
-
-Simply reopen `ActiveRecord::Base` and define a class method like that. You'd typically put this code somewhere in `config/initializers`. You can use this helper like this:
-
-```ruby
-class Movie < ActiveRecord::Base
- validates_as_choice :rating, 5
-end
-```
-
-Working with Validation Errors
-------------------------------
-
-In addition to the `valid?` and `invalid?` methods covered earlier, Rails provides a number of methods for working with the `errors` collection and inquiring about the validity of objects.
-
-The following is a list of the most commonly used methods. Please refer to the `ActiveModel::Errors` documentation for a list of all the available methods.
-
-### `errors`
-
-Returns an instance of the class `ActiveModel::Errors` containing all errors. Each key is the attribute name and the value is an array of strings with all errors.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: true, length: { minimum: 3 }
-end
-
-person = Person.new
-person.valid? # => false
-person.errors
- # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
-
-person = Person.new(name: "John Doe")
-person.valid? # => true
-person.errors # => []
-```
-
-### `errors[]`
-
-`errors[]` is used when you want to check the error messages for a specific attribute. It returns an array of strings with all error messages for the given attribute, each string with one error message. If there are no errors related to the attribute, it returns an empty array.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: true, length: { minimum: 3 }
-end
-
-person = Person.new(name: "John Doe")
-person.valid? # => true
-person.errors[:name] # => []
-
-person = Person.new(name: "JD")
-person.valid? # => false
-person.errors[:name] # => ["is too short (minimum is 3 characters)"]
-
-person = Person.new
-person.valid? # => false
-person.errors[:name]
- # => ["can't be blank", "is too short (minimum is 3 characters)"]
-```
-
-### `errors.add`
-
-The `add` method lets you manually add messages that are related to particular attributes. You can use the `errors.full_messages` or `errors.to_a` methods to view the messages in the form they might be displayed to a user. Those particular messages get the attribute name prepended (and capitalized). `add` receives the name of the attribute you want to add the message to, and the message itself.
-
-```ruby
-class Person < ActiveRecord::Base
- def a_method_used_for_validation_purposes
- errors.add(:name, "cannot contain the characters !@#%*()_-+=")
- end
-end
-
-person = Person.create(name: "!@#")
-
-person.errors[:name]
- # => ["cannot contain the characters !@#%*()_-+="]
-
-person.errors.full_messages
- # => ["Name cannot contain the characters !@#%*()_-+="]
-```
-
-Another way to do this is using `[]=` setter
-
-```ruby
- class Person < ActiveRecord::Base
- def a_method_used_for_validation_purposes
- errors[:name] = "cannot contain the characters !@#%*()_-+="
- end
- end
-
- person = Person.create(name: "!@#")
-
- person.errors[:name]
- # => ["cannot contain the characters !@#%*()_-+="]
-
- person.errors.to_a
- # => ["Name cannot contain the characters !@#%*()_-+="]
-```
-
-### `errors[:base]`
-
-You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message.
-
-```ruby
-class Person < ActiveRecord::Base
- def a_method_used_for_validation_purposes
- errors[:base] << "This person is invalid because ..."
- end
-end
-```
-
-### `errors.clear`
-
-The `clear` method is used when you intentionally want to clear all the messages in the `errors` collection. Of course, calling `errors.clear` upon an invalid object won't actually make it valid: the `errors` collection will now be empty, but the next time you call `valid?` or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the `errors` collection will be filled again.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: true, length: { minimum: 3 }
-end
-
-person = Person.new
-person.valid? # => false
-person.errors[:name]
- # => ["can't be blank", "is too short (minimum is 3 characters)"]
-
-person.errors.clear
-person.errors.empty? # => true
-
-p.save # => false
-
-p.errors[:name]
-# => ["can't be blank", "is too short (minimum is 3 characters)"]
-```
-
-### `errors.size`
-
-The `size` method returns the total number of error messages for the object.
-
-```ruby
-class Person < ActiveRecord::Base
- validates :name, presence: true, length: { minimum: 3 }
-end
-
-person = Person.new
-person.valid? # => false
-person.errors.size # => 2
-
-person = Person.new(name: "Andrea", email: "andrea@example.com")
-person.valid? # => true
-person.errors.size # => 0
-```
-
-Displaying Validation Errors in the View
-----------------------------------------
-
-[DynamicForm](https://github.com/joelmoss/dynamic_form) provides helpers to display the error messages of your models in your view templates.
-
-You can install it as a gem by adding this line to your Gemfile:
-
-```ruby
-gem "dynamic_form"
-```
-
-Now you will have access to the two helper methods `error_messages` and `error_messages_for` in your view templates.
-
-### `error_messages` and `error_messages_for`
-
-When creating a form with the `form_for` helper, you can use the `error_messages` method on the form builder to render all failed validation messages for the current model instance.
-
-```ruby
-class Product < ActiveRecord::Base
- validates :description, :value, presence: true
- validates :value, numericality: true, allow_nil: true
-end
-```
-
-```erb
-<%= form_for(@product) do |f| %>
- <%= f.error_messages %>
- <p>
- <%= f.label :description %><br />
- <%= f.text_field :description %>
- </p>
- <p>
- <%= f.label :value %><br />
- <%= f.text_field :value %>
- </p>
- <p>
- <%= f.submit "Create" %>
- </p>
-<% end %>
-```
-
-If you submit the form with empty fields, the result will be similar to the one shown below:
-
-![Error messages](images/error_messages.png)
-
-NOTE: The appearance of the generated HTML will be different from the one shown, unless you have used scaffolding. See [Customizing the Error Messages CSS](#customizing-the-error-messages-css).
-
-You can also use the `error_messages_for` helper to display the error messages of a model assigned to a view template. It is very similar to the previous example and will achieve exactly the same result.
-
-```erb
-<%= error_messages_for :product %>
-```
-
-The displayed text for each error message will always be formed by the capitalized name of the attribute that holds the error, followed by the error message itself.
-
-Both the `form.error_messages` and the `error_messages_for` helpers accept options that let you customize the `div` element that holds the messages, change the header text, change the message below the header, and specify the tag used for the header element. For example,
-
-```erb
-<%= f.error_messages header_message: "Invalid product!",
- message: "You'll need to fix the following fields:",
- header_tag: :h3 %>
-```
-
-results in:
-
-![Customized error messages](images/customized_error_messages.png)
-
-If you pass `nil` in any of these options, the corresponding section of the `div` will be discarded.
-
-### Customizing the Error Messages CSS
-
-The selectors used to customize the style of error messages are:
-
-* `.field_with_errors` - Style for the form fields and labels with errors.
-* `#error_explanation` - Style for the `div` element with the error messages.
-* `#error_explanation h2` - Style for the header of the `div` element.
-* `#error_explanation p` - Style for the paragraph holding the message that appears right below the header of the `div` element.
-* `#error_explanation ul li` - Style for the list items with individual error messages.
-
-If scaffolding was used, file `app/assets/stylesheets/scaffolds.css.scss` will have been generated automatically. This file defines the red-based styles you saw in the examples above.
-
-The name of the class and the id can be changed with the `:class` and `:id` options, accepted by both helpers.
-
-### Customizing the Error Messages HTML
-
-By default, form fields with errors are displayed enclosed by a `div` element with the `field_with_errors` CSS class. However, it's possible to override that.
-
-The way form fields with errors are treated is defined by `ActionView::Base.field_error_proc`. This is a `Proc` that receives two parameters:
-
-* A string with the HTML tag
-* An instance of `ActionView::Helpers::InstanceTag`.
-
-Below is a simple example where we change the Rails behavior to always display the error messages in front of each of the form fields in error. The error messages will be enclosed by a `span` element with a `validation-error` CSS class. There will be no `div` element enclosing the `input` element, so we get rid of that red border around the text field. You can use the `validation-error` CSS class to style it anyway you want.
-
-```ruby
-ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
- errors = Array(instance.error_message).join(',')
- %(#{html_tag}<span class="validation-error">&nbsp;#{errors}</span>).html_safe
-end
-```
-
-The result looks like the following:
-
-![Validation error messages](images/validation_error_messages.png)
-
-Callbacks Overview
-------------------
-
-Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it is possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
-
-### Callback Registration
-
-In order to use the available callbacks, you need to register them. You can implement the callbacks as ordinary methods and use a macro-style class method to register them as callbacks:
-
-```ruby
-class User < ActiveRecord::Base
- validates :login, :email, presence: true
-
- before_validation :ensure_login_has_a_value
-
- protected
- def ensure_login_has_a_value
- if login.nil?
- self.login = email unless email.blank?
- end
- end
-end
-```
-
-The macro-style class methods can also receive a block. Consider using this style if the code inside your block is so short that it fits in a single line:
-
-```ruby
-class User < ActiveRecord::Base
- validates :login, :email, presence: true
-
- before_create do |user|
- user.name = user.login.capitalize if user.name.blank?
- end
-end
-```
-
-Callbacks can also be registered to only fire on certain lifecycle events:
-
-```ruby
-class User < ActiveRecord::Base
- before_validation :normalize_name, on: :create
-
- # :on takes an array as well
- after_validation :set_location, on: [ :create, :update ]
-
- protected
- def normalize_name
- self.name = self.name.downcase.titleize
- end
-
- def set_location
- self.location = LocationService.query(self)
- end
-end
-```
-
-It is considered good practice to declare callback methods as protected or private. If left public, they can be called from outside of the model and violate the principle of object encapsulation.
-
-Available Callbacks
--------------------
-
-Here is a list with all the available Active Record callbacks, listed in the same order in which they will get called during the respective operations:
-
-### Creating an Object
-
-* `before_validation`
-* `after_validation`
-* `before_save`
-* `around_save`
-* `before_create`
-* `around_create`
-* `after_create`
-* `after_save`
-
-### Updating an Object
-
-* `before_validation`
-* `after_validation`
-* `before_save`
-* `around_save`
-* `before_update`
-* `around_update`
-* `after_update`
-* `after_save`
-
-### Destroying an Object
-
-* `before_destroy`
-* `around_destroy`
-* `after_destroy`
-
-WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
-
-### `after_initialize` and `after_find`
-
-The `after_initialize` callback will be called whenever an Active Record object is instantiated, either by directly using `new` or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record `initialize` method.
-
-The `after_find` callback will be called whenever Active Record loads a record from the database. `after_find` is called before `after_initialize` if both are defined.
-
-The `after_initialize` and `after_find` callbacks have no `before_*` counterparts, but they can be registered just like the other Active Record callbacks.
-
-```ruby
-class User < ActiveRecord::Base
- after_initialize do |user|
- puts "You have initialized an object!"
- end
-
- after_find do |user|
- puts "You have found an object!"
- end
-end
-
->> User.new
-You have initialized an object!
-=> #<User id: nil>
-
->> User.first
-You have found an object!
-You have initialized an object!
-=> #<User id: 1>
-```
-
-Running Callbacks
------------------
-
-The following methods trigger callbacks:
-
-* `create`
-* `create!`
-* `decrement!`
-* `destroy`
-* `destroy_all`
-* `increment!`
-* `save`
-* `save!`
-* `save(validate: false)`
-* `toggle!`
-* `update`
-* `update_attribute`
-* `update_attributes`
-* `update_attributes!`
-* `valid?`
-
-Additionally, the `after_find` callback is triggered by the following finder methods:
-
-* `all`
-* `first`
-* `find`
-* `find_all_by_*`
-* `find_by_*`
-* `find_by_*!`
-* `find_by_sql`
-* `last`
-
-The `after_initialize` callback is triggered every time a new object of the class is initialized.
-
-NOTE: The `find_all_by_*`, `find_by_*` and `find_by_*!` methods are dynamic finders generated automatically for every attribute. Learn more about them at the [Dynamic finders section](active_record_querying.html#dynamic-finders)
-
-Skipping Callbacks
-------------------
-
-Just as with validations, it is also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data.
-
-* `decrement`
-* `decrement_counter`
-* `delete`
-* `delete_all`
-* `increment`
-* `increment_counter`
-* `toggle`
-* `touch`
-* `update_column`
-* `update_columns`
-* `update_all`
-* `update_counters`
-
-Halting Execution
------------------
-
-As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
-
-The whole callback chain is wrapped in a transaction. If any <em>before</em> callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; <em>after</em> callbacks can only accomplish that by raising an exception.
-
-WARNING. Raising an arbitrary exception may break code that expects `save` and its friends not to fail like that. The `ActiveRecord::Rollback` exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised.
-
-Relational Callbacks
---------------------
-
-Callbacks work through model relationships, and can even be defined by them. Suppose an example where a user has many posts. A user's posts should be destroyed if the user is destroyed. Let's add an `after_destroy` callback to the `User` model by way of its relationship to the `Post` model:
-
-```ruby
-class User < ActiveRecord::Base
- has_many :posts, dependent: :destroy
-end
-
-class Post < ActiveRecord::Base
- after_destroy :log_destroy_action
-
- def log_destroy_action
- puts 'Post destroyed'
- end
-end
-
->> user = User.first
-=> #<User id: 1>
->> user.posts.create!
-=> #<Post id: 1, user_id: 1>
->> user.destroy
-Post destroyed
-=> #<User id: 1>
-```
-
-Conditional Callbacks
----------------------
-
-As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the `:if` and `:unless` options, which can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if` option when you want to specify under which conditions the callback **should** be called. If you want to specify the conditions under which the callback **should not** be called, then you may use the `:unless` option.
-
-### Using `:if` and `:unless` with a `Symbol`
-
-You can associate the `:if` and `:unless` options with a symbol corresponding to the name of a predicate method that will get called right before the callback. When using the `:if` option, the callback won't be executed if the predicate method returns false; when using the `:unless` option, the callback won't be executed if the predicate method returns true. This is the most common option. Using this form of registration it is also possible to register several different predicates that should be called to check if the callback should be executed.
-
-```ruby
-class Order < ActiveRecord::Base
- before_save :normalize_card_number, if: :paid_with_card?
-end
-```
-
-### Using `:if` and `:unless` with a String
-
-You can also use a string that will be evaluated using `eval` and hence needs to contain valid Ruby code. You should use this option only when the string represents a really short condition:
-
-```ruby
-class Order < ActiveRecord::Base
- before_save :normalize_card_number, if: "paid_with_card?"
-end
-```
-
-### Using `:if` and `:unless` with a `Proc`
-
-Finally, it is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners:
-
-```ruby
-class Order < ActiveRecord::Base
- before_save :normalize_card_number,
- if: Proc.new { |order| order.paid_with_card? }
-end
-```
-
-### Multiple Conditions for Callbacks
-
-When writing conditional callbacks, it is possible to mix both `:if` and `:unless` in the same callback declaration:
-
-```ruby
-class Comment < ActiveRecord::Base
- after_create :send_email_to_author, if: :author_wants_emails?,
- unless: Proc.new { |comment| comment.post.ignore_comments? }
-end
-```
-
-Callback Classes
-----------------
-
-Sometimes the callback methods that you'll write will be useful enough to be reused by other models. Active Record makes it possible to create classes that encapsulate the callback methods, so it becomes very easy to reuse them.
-
-Here's an example where we create a class with an `after_destroy` callback for a `PictureFile` model:
-
-```ruby
-class PictureFileCallbacks
- def after_destroy(picture_file)
- if File.exists?(picture_file.filepath)
- File.delete(picture_file.filepath)
- end
- end
-end
-```
-
-When declared inside a class, as above, the callback methods will receive the model object as a parameter. We can now use the callback class in the model:
-
-```ruby
-class PictureFile < ActiveRecord::Base
- after_destroy PictureFileCallbacks.new
-end
-```
-
-Note that we needed to instantiate a new `PictureFileCallbacks` object, since we declared our callback as an instance method. This is particularly useful if the callbacks make use of the state of the instantiated object. Often, however, it will make more sense to declare the callbacks as class methods:
-
-```ruby
-class PictureFileCallbacks
- def self.after_destroy(picture_file)
- if File.exists?(picture_file.filepath)
- File.delete(picture_file.filepath)
- end
- end
-end
-```
-
-If the callback method is declared this way, it won't be necessary to instantiate a `PictureFileCallbacks` object.
-
-```ruby
-class PictureFile < ActiveRecord::Base
- after_destroy PictureFileCallbacks
-end
-```
-
-You can declare as many callbacks as you want inside your callback classes.
-
-Observers
----------
-
-Observers are similar to callbacks, but with important differences. Whereas callbacks can pollute a model with code that isn't directly related to its purpose, observers allow you to add the same functionality without changing the code of the model. For example, it could be argued that a `User` model should not include code to send registration confirmation emails. Whenever you use callbacks with code that isn't directly related to your model, you may want to consider creating an observer instead.
-
-### Creating Observers
-
-For example, imagine a `User` model where we want to send an email every time a new user is created. Because sending emails is not directly related to our model's purpose, we should create an observer to contain the code implementing this functionality.
-
-```bash
-$ rails generate observer User
-```
-
-generates `app/models/user_observer.rb` containing the observer class `UserObserver`:
-
-```ruby
-class UserObserver < ActiveRecord::Observer
-end
-```
-
-You may now add methods to be called at the desired occasions:
-
-```ruby
-class UserObserver < ActiveRecord::Observer
- def after_create(model)
- # code to send confirmation email...
- end
-end
-```
-
-As with callback classes, the observer's methods receive the observed model as a parameter.
-
-### Registering Observers
-
-Observers are conventionally placed inside of your `app/models` directory and registered in your application's `config/application.rb` file. For example, the `UserObserver` above would be saved as `app/models/user_observer.rb` and registered in `config/application.rb` this way:
-
-```ruby
-# Activate observers that should always be running.
-config.active_record.observers = :user_observer
-```
-
-As usual, settings in `config/environments` take precedence over those in `config/application.rb`. So, if you prefer that an observer doesn't run in all environments, you can simply register it in a specific environment instead.
-
-### Sharing Observers
-
-By default, Rails will simply strip "Observer" from an observer's name to find the model it should observe. However, observers can also be used to add behavior to more than one model, and thus it is possible to explicitly specify the models that our observer should observe:
-
-```ruby
-class MailerObserver < ActiveRecord::Observer
- observe :registration, :user
-
- def after_create(model)
- # code to send confirmation email...
- end
-end
-```
-
-In this example, the `after_create` method will be called whenever a `Registration` or `User` is created. Note that this new `MailerObserver` would also need to be registered in `config/application.rb` in order to take effect:
-
-```ruby
-# Activate observers that should always be running.
-config.active_record.observers = :mailer_observer
-```
-
-Transaction Callbacks
----------------------
-
-There are two additional callbacks that are triggered by the completion of a database transaction: `after_commit` and `after_rollback`. These callbacks are very similar to the `after_save` callback except that they don't execute until after database changes have either been committed or rolled back. They are most useful when your active record models need to interact with external systems which are not part of the database transaction.
-
-Consider, for example, the previous example where the `PictureFile` model needs to delete a file after the corresponding record is destroyed. If anything raises an exception after the `after_destroy` callback is called and the transaction rolls back, the file will have been deleted and the model will be left in an inconsistent state. For example, suppose that `picture_file_2` in the code below is not valid and the `save!` method raises an error.
-
-```ruby
-PictureFile.transaction do
- picture_file_1.destroy
- picture_file_2.save!
-end
-```
-
-By using the `after_commit` callback we can account for this case.
-
-```ruby
-class PictureFile < ActiveRecord::Base
- attr_accessor :delete_file
-
- after_destroy do |picture_file|
- picture_file.delete_file = picture_file.filepath
- end
-
- after_commit do |picture_file|
- if picture_file.delete_file && File.exist?(picture_file.delete_file)
- File.delete(picture_file.delete_file)
- picture_file.delete_file = nil
- end
- end
-end
-```
-
-The `after_commit` and `after_rollback` callbacks are guaranteed to be called for all models created, updated, or destroyed within a transaction block. If any exceptions are raised within one of these callbacks, they will be ignored so that they don't interfere with the other callbacks. As such, if your callback code could raise an exception, you'll need to rescue it and handle it appropriately within the callback.
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 401e6f0596..7f03363b23 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -5,7 +5,12 @@ Active Support is the Ruby on Rails component responsible for providing Ruby lan
It offers a richer bottom-line at the language level, targeted both at the development of Rails applications, and at the development of Ruby on Rails itself.
-By referring to this guide you will learn the extensions to the Ruby core classes and modules provided by Active Support.
+After reading this guide, you will know:
+
+* What Core Extensions are.
+* How to load all extensions.
+* How to cherry-pick just the extensions you want.
+* What extensions ActiveSupport provides.
--------------------------------------------------------------------------------
@@ -915,7 +920,7 @@ When interpolated into a string, the `:to` option should become an expression th
delegate :logger, to: :Rails
# delegates to the receiver's class
-delegate :table_name, to: 'self.class'
+delegate :table_name, to: :class
```
WARNING: If the `:prefix` option is `true` this is less generic, see below.
@@ -1120,8 +1125,6 @@ C.subclasses # => [B, D]
The order in which these classes are returned is unspecified.
-WARNING: This method is redefined in some Rails core classes but should be all compatible in Rails 3.1.
-
NOTE: Defined in `active_support/core_ext/class/subclasses.rb`.
#### `descendants`
@@ -1157,7 +1160,7 @@ Inserting data into HTML templates needs extra care. For example, you can't just
#### Safe Strings
-Active Support has the concept of <i>(html) safe</i> strings since Rails 3. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not.
+Active Support has the concept of <i>(html) safe</i> strings. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not.
Strings are considered to be <i>unsafe</i> by default:
@@ -1194,10 +1197,10 @@ Safe arguments are directly appended:
"".html_safe + "<".html_safe # => "<"
```
-These methods should not be used in ordinary views. In Rails 3 unsafe values are automatically escaped:
+These methods should not be used in ordinary views. Unsafe values are automatically escaped:
```erb
-<%= @review.title %> <%# fine in Rails 3, escaped if needed %>
+<%= @review.title %> <%# fine, escaped if needed %>
```
To insert something verbatim use the `raw` helper rather than calling `html_safe`:
@@ -2067,14 +2070,6 @@ The sum of an empty receiver can be customized in this form as well:
[].sum(1) {|n| n**3} # => 1
```
-The method `ActiveRecord::Observer#observed_subclasses` for example is implemented this way:
-
-```ruby
-def observed_subclasses
- observed_classes.sum([]) { |klass| klass.send(:subclasses) }
-end
-```
-
NOTE: Defined in `active_support/core_ext/enumerable.rb`.
### `index_by`
@@ -2418,9 +2413,9 @@ or yields them in turn if a block is passed:
```html+erb
<% sample.in_groups_of(3) do |a, b, c| %>
<tr>
- <td><%=h a %></td>
- <td><%=h b %></td>
- <td><%=h c %></td>
+ <td><%= a %></td>
+ <td><%= b %></td>
+ <td><%= c %></td>
</tr>
<% end %>
```
@@ -2683,13 +2678,6 @@ If the receiver responds to `convert_key`, the method is called on each of the a
{a: 1}.with_indifferent_access.except("a") # => {}
```
-The method `except` may come in handy for example when you want to protect some parameter that can't be globally protected with `attr_protected`:
-
-```ruby
-params[:account] = params[:account].except(:plan_id) unless admin?
-@account.update_attributes(params[:account])
-```
-
There's also the bang variant `except!` that removes keys in the very receiver.
NOTE: Defined in `active_support/core_ext/hash/except.rb`.
@@ -3604,7 +3592,7 @@ Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
# In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST.
-t = Time.local_time(2010, 3, 28, 1, 59, 59)
+t = Time.local(2010, 3, 28, 1, 59, 59)
# => Sun Mar 28 01:59:59 +0100 2010
t.advance(seconds: 1)
# => Sun Mar 28 03:00:00 +0200 2010
@@ -3659,26 +3647,6 @@ Time.current
Analogously to `DateTime`, the predicates `past?`, and `future?` are relative to `Time.current`.
-Use the `local_time` class method to create time objects honoring the user time zone:
-
-```ruby
-Time.zone_default
-# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
-Time.local_time(2010, 8, 15)
-# => Sun Aug 15 00:00:00 +0200 2010
-```
-
-The `utc_time` class method returns a time in UTC:
-
-```ruby
-Time.zone_default
-# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
-Time.utc_time(2010, 8, 15)
-# => Sun Aug 15 00:00:00 UTC 2010
-```
-
-Both `local_time` and `utc_time` accept up to seven positional arguments: year, month, day, hour, min, sec, usec. Year is mandatory, month and day default to 1, and the rest default to 0.
-
If the time to be constructed lies beyond the range supported by `Time` in the runtime platform, usecs are discarded and a `DateTime` object is returned instead.
#### Durations
@@ -3697,7 +3665,7 @@ now - 1.week
They translate to calls to `since` or `advance`. For example here we get the correct jump in the calendar reform:
```ruby
-Time.utc_time(1582, 10, 3) + 5.days
+Time.utc(1582, 10, 3) + 5.days
# => Mon Oct 18 00:00:00 UTC 1582
```
@@ -3728,6 +3696,25 @@ The auxiliary file is written in a standard directory for temporary files, but y
NOTE: Defined in `active_support/core_ext/file/atomic.rb`.
+Extensions to `Marshal`
+-----------------------
+
+### `load`
+
+Active Support adds constant autoloading support to `load`.
+
+For example, the file cache store deserializes this way:
+
+```ruby
+File.open(file_name) { |f| Marshal.load(f) }
+```
+
+If the cached data refers to a constant that is unknown at that point, the autoloading mechanism is triggered and if it succeeds the deserialization is retried transparently.
+
+WARNING. If the argument is an `IO` it needs to respond to `rewind` to be able to retry. Regular files respond to `rewind`.
+
+NOTE: Defined in `active_support/core_ext/marshal.rb`.
+
Extensions to `Logger`
----------------------
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 1163940f10..6b3be69942 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -3,19 +3,21 @@ Active Support Instrumentation
Active Support is a part of core Rails that provides Ruby language extensions, utilities and other things. One of the things it includes is an instrumentation API that can be used inside an application to measure certain actions that occur within Ruby code, such as that inside a Rails application or the framework itself. It is not limited to Rails, however. It can be used independently in other Ruby scripts if it is so desired.
-In this guide, you will learn how to use the instrumentation API inside of ActiveSupport to measure events inside of Rails and other Ruby code. We cover:
+In this guide, you will learn how to use the instrumentation API inside of Active Support to measure events inside of Rails and other Ruby code.
-* What instrumentation can provide
-* The hooks inside the Rails framework for instrumentation
-* Adding a subscriber to a hook
-* Building a custom instrumentation implementation
+After reading this guide, you will know:
+
+* What instrumentation can provide.
+* The hooks inside the Rails framework for instrumentation.
+* Adding a subscriber to a hook.
+* Building a custom instrumentation implementation.
--------------------------------------------------------------------------------
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.
+The instrumentation API provided by Active Support allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in <TODO: link to section detailing each hook point>. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
For example, there is a hook provided within Active Record that is called every time Active Record uses an SQL query on a database. This hook could be **subscribed** to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
@@ -26,8 +28,8 @@ Rails framework hooks
Within the Ruby on Rails framework, there are a number of hooks provided for common events. These are detailed below.
-ActionController
-----------------
+Action Controller
+-----------------
### write_fragment.action_controller
@@ -187,8 +189,8 @@ INFO. Additional keys may be added by the caller.
}
```
-ActionView
-----------
+Action View
+-----------
### render_template.action_view
@@ -216,7 +218,7 @@ ActionView
}
```
-ActiveRecord
+Active Record
------------
### sql.active_record
@@ -246,8 +248,8 @@ INFO. The adapters will add their own data as well.
| `:name` | Record's class |
| `:connection_id` | `self.object_id` |
-ActionMailer
-------------
+Action Mailer
+-------------
### receive.action_mailer
@@ -312,8 +314,8 @@ ActiveResource
| `:request_uri` | Complete URI |
| `:result` | HTTP response object |
-ActiveSupport
--------------
+Active Support
+--------------
### cache_read.active_support
@@ -430,7 +432,7 @@ from block args like this:
```ruby
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
- event = ActiveSupport::Notification::Event.new args
+ event = ActiveSupport::Notifications::Event.new *args
event.name # => "process_action.action_controller"
event.duration # => 10 (in milliseconds)
diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md
index 72e412e701..d0499878da 100644
--- a/guides/source/api_documentation_guidelines.md
+++ b/guides/source/api_documentation_guidelines.md
@@ -3,6 +3,11 @@ API Documentation Guidelines
This guide documents the Ruby on Rails API documentation guidelines.
+After reading this guide, you will know:
+
+* How to write effective prose for documentation purposes.
+* Style guidelines for documenting different kinds of Ruby code.
+
--------------------------------------------------------------------------------
RDoc
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 13df1965b6..b302ef76c6 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1,14 +1,15 @@
-Asset Pipeline
-==============
+The Asset Pipeline
+==================
-This guide covers the asset pipeline introduced in Rails 3.1.
-By referring to this guide you will be able to:
+This guide covers the asset pipeline.
-* Understand what the asset pipeline is and what it does
-* Properly organize your application assets
-* Understand the benefits of the asset pipeline
-* Add a pre-processor to the pipeline
-* Package assets with a gem
+After reading this guide, you will know:
+
+* How to understand what the asset pipeline is and what it does.
+* How to properly organize your application assets.
+* How to understand the benefits of the asset pipeline.
+* How to add a pre-processor to the pipeline.
+* How to package assets with a gem.
--------------------------------------------------------------------------------
@@ -17,11 +18,9 @@ What is the Asset Pipeline?
The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, Sass and ERB.
-Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 is integrated with Sprockets through Action Pack which depends on the `sprockets` gem, by default.
-
Making the asset pipeline a core feature of Rails means that all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "fast by default" strategy as outlined by DHH in his keynote at RailsConf 2011.
-In Rails 3.1, the asset pipeline is enabled by default. It can be disabled in `config/application.rb` by putting this line inside the application class definition:
+The asset pipeline is enabled by default. It can be disabled in `config/application.rb` by putting this line inside the application class definition:
```ruby
config.assets.enabled = false
@@ -97,11 +96,25 @@ Assets can still be placed in the `public` hierarchy. Any assets under `public`
In production, Rails precompiles these files to `public/assets` by default. The precompiled copies are then served as static assets by the web server. The files in `app/assets` are never served directly in production.
+### Controller Specific Assets
+
When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller.
-For example, if you generate a `ProjectsController`, Rails will also add a new file at `app/assets/javascripts/projects.js.coffee` and another at `app/assets/stylesheets/projects.css.scss`. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as `<%= javascript_include_tag params[:controller] %>` or `<%= stylesheet_link_tag params[:controller] %>`.
+For example, if you generate a `ProjectsController`, Rails will also add a new file at `app/assets/javascripts/projects.js.coffee` and another at `app/assets/stylesheets/projects.css.scss`. By default these files will be ready to use by your application immediately using the `require_tree` directive. See [Manifest Files and Directives](#manifest-files-and-directives) for more details on require_tree.
+
+You can also opt to include controller specific stylesheets and JavaScript files only in their respective controllers using the following: `<%= javascript_include_tag params[:controller] %>` or `<%= stylesheet_link_tag params[:controller] %>`. Ensure that you are not using the `require_tree` directive though, as this will result in your assets being included more than once.
+
+WARNING: When using asset precompilation (the production default), you will need to ensure that your controller assets will be precompiled when loading them on a per page basis. By default .coffee and .scss files will not be precompiled on their own. This will result in false positives during development as these files will work just fine since assets will be compiled on the fly. When running in production however, you will see 500 errors since live compilation is turned off by default. See [Precompiling Assets](#precompiling-assets) for more information on how precompiling works.
-NOTE: You must have an [ExecJS](https://github.com/sstephenson/execjs#readme) supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes.
+NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. Check [ExecJS](https://github.com/sstephenson/execjs#readme) documentation to know all supported JavaScript runtimes.
+
+You can also disable the generation of asset files when generating a controller by adding the following to your `config/application.rb` configuration:
+
+```ruby
+config.generators do |g|
+ g.assets false
+end
+```
### Asset Organization
@@ -113,7 +126,7 @@ Pipeline assets can be placed inside an application in one of three locations: `
* `vendor/assets` is for assets that are owned by outside entities, such as code for JavaScript plugins and CSS frameworks.
-#### Search paths
+#### Search Paths
When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it.
@@ -161,7 +174,7 @@ Paths are traversed in the order that they occur in the search path. By default,
It is important to note that files you want to reference outside a manifest must be added to the precompile array or they will not be available in the production environment.
-#### Using index files
+#### Using Index Files
Sprockets uses files named `index` (with the relevant extensions) for a special purpose.
@@ -256,7 +269,8 @@ $('#logo').attr src: "<%= asset_path('logo.png') %>"
### Manifest Files and Directives
-Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ — instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if `Rails.application.config.assets.compress` is true). By serving one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests.
+Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ — instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if `Rails.application.config.assets.compress` is true). By serving one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests. Compression also reduces the file size enabling the browser to download it faster.
+
For example, a new Rails application includes a default `app/assets/javascripts/application.js` file which contains the following lines:
@@ -269,8 +283,6 @@ For example, a new Rails application includes a default `app/assets/javascripts/
In JavaScript files, the directives begin with `//=`. In this case, the file is using the `require` and the `require_tree` directives. The `require` directive is used to tell Sprockets the files that you wish to require. Here, you are requiring the files `jquery.js` and `jquery_ujs.js` that are available somewhere in the search path for Sprockets. You need not supply the extensions explicitly. Sprockets assumes you are requiring a `.js` file when done from within a `.js` file.
-NOTE. In Rails 3.1 the `jquery-rails` gem provides the `jquery.js` and `jquery_ujs.js` files via the asset pipeline. You won't see them in the application tree.
-
The `require_tree` directive tells Sprockets to recursively include _all_ JavaScript files in the specified directory into the output. These paths must be specified relative to the manifest file. You can also use the `require_directory` directive which includes all JavaScript files only in the directory specified, without recursion.
Directives are processed top to bottom, but the order in which files are included by `require_tree` is unspecified. You should not rely on any particular order among those. If you need to ensure some particular JavaScript ends up above some other in the concatenated file, require the prerequisite file first in the manifest. Note that the family of `require` directives prevents files from being included twice in the output.
@@ -336,7 +348,7 @@ would generate this HTML:
The `body` param is required by Sprockets.
-### Turning Debugging off
+### Turning Debugging Off
You can turn off debug mode by updating `config/environments/development.rb` to include:
@@ -444,6 +456,27 @@ If you have other manifests or individual stylesheets and JavaScript files to in
config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
```
+Or you can opt to precompile all assets with something like this:
+
+```ruby
+# config/environments/production.rb
+config.assets.precompile << Proc.new { |path|
+ if path =~ /\.(css|js)\z/
+ full_path = Rails.application.assets.resolve(path).to_path
+ app_assets_path = Rails.root.join('app', 'assets').to_path
+ if full_path.starts_with? app_assets_path
+ puts "including asset: " + full_path
+ true
+ else
+ puts "excluding asset: " + full_path
+ false
+ end
+ else
+ false
+ end
+}
+```
+
NOTE. Always specify an expected compiled filename that ends with js or css, even if you want to add Sass or CoffeeScript files to the precompile array.
The rake task also generates a `manifest.yml` that contains a list with all your assets and their respective fingerprints. This is used by the Rails helper methods to avoid handing the mapping requests back to Sprockets. A typical manifest file looks like:
@@ -461,7 +494,7 @@ The default location for the manifest is the root of the location specified in `
NOTE: If there are missing precompiled files in production you will get an `Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError` exception indicating the name of the missing file(s).
-#### Far-future Expires header
+#### Far-future Expires Header
Precompiled assets exist on the filesystem and are served directly by your web server. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add them.
@@ -491,7 +524,7 @@ location ~ ^/assets/ {
}
```
-#### GZip compression
+#### GZip Compression
When files are precompiled, Sprockets also creates a [gzipped](http://en.wikipedia.org/wiki/Gzip) (.gz) version of your assets. Web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, Sprockets uses the maximum compression ratio, thus reducing the size of the data transfer to the minimum. On the other hand, web servers can be configured to serve compressed content directly from disk, rather than deflating non-compressed files themselves.
@@ -647,7 +680,7 @@ This can be changed to something else:
config.assets.prefix = "/some_other_path"
```
-This is a handy option if you are updating an existing project (pre Rails 3.1) that already uses this path or you wish to use this path for a new resource.
+This is a handy option if you are updating an older project that didn't use the asset pipeline and that already uses this path or you wish to use this path for a new resource.
### X-Sendfile Headers
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 9bb5aa8bc2..dd59e2a8df 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -1,11 +1,13 @@
-A Guide to Active Record Associations
-=====================================
+Active Record Associations
+==========================
-This guide covers the association features of Active Record. By referring to this guide, you will be able to:
+This guide covers the association features of Active Record.
-* Declare associations between Active Record models
-* Understand the various types of Active Record associations
-* Use the methods added to your models by creating associations
+After reading this guide, you will know:
+
+* How to declare associations between Active Record models.
+* How to understand the various types of Active Record associations.
+* How to use the methods added to your models by creating associations.
--------------------------------------------------------------------------------
@@ -92,6 +94,25 @@ end
NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `customer` association in the `Order` model, you would be told that there was an "uninitialized constant Order::Customers". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.
+The corresponding migration might look like this:
+
+```ruby
+class CreateOrders < ActiveRecord::Migration
+ def change
+ create_table :customers do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :orders do |t|
+ t.belongs_to :customer
+ t.datetime :order_date
+ t.timestamps
+ end
+ end
+end
+```
+
### The `has_one` Association
A `has_one` association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model. For example, if each supplier in your application has only one account, you'd declare the supplier model like this:
@@ -104,6 +125,25 @@ end
![has_one Association Diagram](images/has_one.png)
+The corresponding migration might look like this:
+
+```ruby
+class CreateSuppliers < ActiveRecord::Migration
+ def change
+ create_table :suppliers do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :accounts do |t|
+ t.belongs_to :supplier
+ t.string :account_number
+ t.timestamps
+ end
+ end
+end
+```
+
### The `has_many` Association
A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
@@ -118,6 +158,25 @@ NOTE: The name of the other model is pluralized when declaring a `has_many` asso
![has_many Association Diagram](images/has_many.png)
+The corresponding migration might look like this:
+
+```ruby
+class CreateCustomers < ActiveRecord::Migration
+ def change
+ create_table :customers do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :orders do |t|
+ t.belongs_to :customer
+ t.datetime :order_date
+ t.timestamps
+ end
+ end
+end
+```
+
### The `has_many :through` Association
A `has_many :through` association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding _through_ a third model. For example, consider a medical practice where patients make appointments to see physicians. The relevant association declarations could look like this:
@@ -141,6 +200,31 @@ end
![has_many :through Association Diagram](images/has_many_through.png)
+The corresponding migration might look like this:
+
+```ruby
+class CreateAppointments < ActiveRecord::Migration
+ def change
+ create_table :physicians do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :patients do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :appointments do |t|
+ t.belongs_to :physician
+ t.belongs_to :patient
+ t.datetime :appointment_date
+ t.timestamps
+ end
+ end
+end
+```
+
The collection of join models can be managed via the API. For example, if you assign
```ruby
@@ -197,6 +281,31 @@ end
![has_one :through Association Diagram](images/has_one_through.png)
+The corresponding migration might look like this:
+
+```ruby
+class CreateAccountHistories < ActiveRecord::Migration
+ def change
+ create_table :suppliers do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :accounts do |t|
+ t.belongs_to :supplier
+ t.string :account_number
+ t.timestamps
+ end
+
+ create_table :account_histories do |t|
+ t.belongs_to :account
+ t.integer :credit_rating
+ t.timestamps
+ end
+ end
+end
+```
+
### The `has_and_belongs_to_many` Association
A `has_and_belongs_to_many` association creates a direct many-to-many connection with another model, with no intervening model. For example, if your application includes assemblies and parts, with each assembly having many parts and each part appearing in many assemblies, you could declare the models this way:
@@ -213,6 +322,29 @@ end
![has_and_belongs_to_many Association Diagram](images/habtm.png)
+The corresponding migration might look like this:
+
+```ruby
+class CreateAssembliesAndParts < ActiveRecord::Migration
+ def change
+ create_table :assemblies do |t|
+ t.string :name
+ t.timestamps
+ end
+
+ create_table :parts do |t|
+ t.string :part_number
+ t.timestamps
+ end
+
+ create_table :assemblies_parts do |t|
+ t.belongs_to :assembly
+ t.belongs_to :part
+ end
+ end
+end
+```
+
### Choosing Between `belongs_to` and `has_one`
If you want to set up a one-to-one relationship between two models, you'll need to add `belongs_to` to one, and `has_one` to the other. How do you know which is which?
@@ -450,7 +582,7 @@ class CreateAssemblyPartJoinTable < ActiveRecord::Migration
end
```
-We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled models IDs, or exceptions about conflicting IDs chances are you forgot that bit.
+We pass `id: false` to `create_table` because that table does not represent a model. That's required for the association to work properly. If you observe any strange behavior in a `has_and_belongs_to_many` association like mangled models IDs, or exceptions about conflicting IDs, chances are you forgot that bit.
### Controlling Association Scope
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 4cb76bfe5f..773102400a 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -3,12 +3,12 @@ Caching with Rails: An overview
This guide will teach you what you need to know about avoiding that expensive round-trip to your database and returning what you need to return to the web clients in the shortest time possible.
-After reading this guide, you should be able to use and configure:
+After reading this guide, you will know:
-* Page, action, and fragment caching
-* Sweepers
-* Alternative cache stores
-* Conditional GET support
+* Page, action, and fragment caching.
+* Sweepers.
+* Alternative cache stores.
+* Conditional GET support.
--------------------------------------------------------------------------------
@@ -67,8 +67,6 @@ class ProductsController < ActionController
end
```
-If you want a more complicated expiration scheme, you can use cache sweepers to expire cached objects when things change. This is covered in the section on Sweepers.
-
By default, page caching automatically gzips files (for example, to `products.html.gz` if user requests `/products`) to reduce the size of data transmitted (web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, compression ratio is maximum).
Nginx is able to serve compressed content directly from disk by enabling `gzip_static`:
@@ -106,7 +104,7 @@ Let's say you only wanted authenticated users to call actions on `ProductsContro
```ruby
class ProductsController < ActionController
- before_filter :authenticate
+ before_action :authenticate
caches_action :index
def index
@@ -176,102 +174,6 @@ This fragment is then available to all actions in the `ProductsController` using
expire_fragment('all_available_products')
```
-### Sweepers
-
-Cache sweeping is a mechanism which allows you to get around having a ton of `expire_{page,action,fragment}` calls in your code. It does this by moving all the work required to expire cached content into an `ActionController::Caching::Sweeper` subclass. This class is an observer and looks for changes to an Active Record object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter.
-
-TIP: Sweepers rely on the use of Active Record and Active Record Observers. The object you are observing must be an Active Record model.
-
-Continuing with our Product controller example, we could rewrite it with a sweeper like this:
-
-```ruby
-class ProductSweeper < ActionController::Caching::Sweeper
- observe Product # This sweeper is going to keep an eye on the Product model
-
- # If our sweeper detects that a Product was created call this
- def after_create(product)
- expire_cache_for(product)
- end
-
- # If our sweeper detects that a Product was updated call this
- def after_update(product)
- expire_cache_for(product)
- end
-
- # If our sweeper detects that a Product was deleted call this
- def after_destroy(product)
- expire_cache_for(product)
- end
-
- private
- def expire_cache_for(product)
- # Expire the index page now that we added a new product
- expire_page(controller: 'products', action: 'index')
-
- # Expire a fragment
- expire_fragment('all_available_products')
- end
-end
-```
-
-You may notice that the actual product gets passed to the sweeper, so if we were caching the edit action for each product, we could add an expire method which specifies the page we want to expire:
-
-```ruby
-expire_action(controller: 'products', action: 'edit', id: product.id)
-```
-
-Then we add it to our controller to tell it to call the sweeper when certain actions are called. So, if we wanted to expire the cached content for the list and edit actions when the create action was called, we could do the following:
-
-```ruby
-class ProductsController < ActionController
-
- before_filter :authenticate
- caches_action :index
- cache_sweeper :product_sweeper
-
- def index
- @products = Product.all
- end
-
-end
-```
-
-Sometimes it is necessary to disambiguate the controller when you call `expire_action`, such as when there are two identically named controllers in separate namespaces:
-
-```ruby
-class ProductsController < ActionController
- caches_action :index
-
- def index
- @products = Product.all
- end
-end
-
-module Admin
- class ProductsController < ActionController
- cache_sweeper :product_sweeper
-
- def new
- @product = Product.new
- end
-
- def create
- @product = Product.create(params[:product])
- end
- end
-end
-
-class ProductSweeper < ActionController::Caching::Sweeper
- observe Product
-
- def after_create(product)
- expire_action(controller: '/products', action: 'index')
- end
-end
-```
-
-Note the use of '/products' here rather than 'products'. If you wanted to expire an action cache for the `Admin::ProductsController`, you would use 'admin/products' instead.
-
### SQL Caching
Query caching is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.
@@ -465,14 +367,14 @@ end
Instead of a options hash, you can also simply pass in a model, Rails will use the `updated_at` and `cache_key` methods for setting `last_modified` and `etag`:
-<ruby>
+```ruby
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
respond_with(@product) if stale?(@product)
end
end
-</ruby>
+```
If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using respond_to or calling render yourself) then you’ve got an easy helper in fresh_when:
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 9521212581..fb15790d90 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -1,20 +1,20 @@
-A Guide to The Rails Command Line
-=================================
+The Rails Command Line
+======================
Rails comes with every command line tool you'll need to
-* Create a Rails application
-* Generate models, controllers, database migrations, and unit tests
-* Start a development server
-* Experiment with objects through an interactive shell
-* Profile and benchmark your new creation
+After reading this guide, you will know:
+
+* How to create a Rails application.
+* How to generate models, controllers, database migrations, and unit tests.
+* How to start a development server.
+* How to experiment with objects through an interactive shell.
+* How to profile and benchmark your new creation.
--------------------------------------------------------------------------------
NOTE: This tutorial assumes you have basic Rails knowledge from reading the [Getting Started with Rails Guide](getting_started.html).
-WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
-
Command Line Basics
-------------------
@@ -377,7 +377,7 @@ Active Record version 4.0.0.beta
Action Pack version 4.0.0.beta
Action Mailer version 4.0.0.beta
Active Support version 4.0.0.beta
-Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport
+Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::EncryptedCookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport
Application root /home/foobar/commandsapp
Environment development
Database adapter sqlite3
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index ac763d6e0e..5fe8e2fba6 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -1,10 +1,12 @@
Configuring Rails Applications
==============================
-This guide covers the configuration and initialization features available to Rails applications. By referring to this guide, you will be able to:
+This guide covers the configuration and initialization features available to Rails applications.
-* Adjust the behavior of your Rails applications
-* Add additional code to be run at application start time
+After reading this guide, you will know:
+
+* How to adjust the behavior of your Rails applications.
+* How to add additional code to be run at application start time.
--------------------------------------------------------------------------------
@@ -37,7 +39,7 @@ config.filter_parameters += [:password]
This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same `config` object in `config/application.rb`:
```ruby
-config.active_record.observers = [:hotel_observer, :review_observer]
+config.active_record.schema_format = :ruby
```
Rails will use that particular setting to configure Active Record.
@@ -103,14 +105,10 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id — both very helpful in debugging multi-user production applications.
-* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::BufferedLogger`, with auto flushing off in production mode.
+* `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`, with auto flushing off in production mode.
* `config.middleware` allows you to configure the application's middleware. This is covered in depth in the [Configuring Middleware](#configuring-middleware) section below.
-* `config.queue` configures the default job queue for the application. Defaults to `ActiveSupport::Queue.new` which processes jobs in a background thread. If you change the queue, you're responsible for running the jobs as well.
-
-* `config.queue_consumer` configures a different job consumer for the default queue. Defaults to `ActiveSupport::ThreadedQueueConsumer`. The job consumer must respond to `start`.
-
* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If `config.cache_classes` is true, this option is ignored.
* `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`.
@@ -133,8 +131,6 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
### Configuring Assets
-Rails 3.1 and up, by default, is set up to use the `sprockets` gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful.
-
* `config.assets.enabled` a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in `config/application.rb`.
* `config.assets.compress` a flag that enables the compression of compiled assets. It is explicitly set to true in `config/production.rb`.
@@ -163,7 +159,7 @@ Rails 3.1 and up, by default, is set up to use the `sprockets` gem to manage ass
### Configuring Generators
-Rails 3 allows you to alter what generators are used with the `config.generators` method. This method takes a block:
+Rails allows you to alter what generators are used with the `config.generators` method. This method takes a block:
```ruby
config.generators do |g|
@@ -201,7 +197,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* `Rails::Rack::Logger` notifies the logs that the request has began. After request is complete, flushes all the logs.
* `ActionDispatch::ShowExceptions` rescues any exception returned by the application and renders nice exception pages if the request is local or if `config.consider_all_requests_local` is set to `true`. If `config.action_dispatch.show_exceptions` is set to `false`, exceptions will be raised regardless.
* `ActionDispatch::RequestId` makes a unique X-Request-Id header available to the response and enables the `ActionDispatch::Request#uuid` method.
-* `ActionDispatch::RemoteIp` checks for IP spoofing attacks. Configurable with the `config.action_dispatch.ip_spoofing_check` and `config.action_dispatch.trusted_proxies` settings.
+* `ActionDispatch::RemoteIp` checks for IP spoofing attacks and gets valid `client_ip` from request headers. Configurable with the `config.action_dispatch.ip_spoofing_check`, and `config.action_dispatch.trusted_proxies` options.
* `Rack::Sendfile` intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with `config.action_dispatch.x_sendfile_header`.
* `ActionDispatch::Callbacks` runs the prepare callbacks before serving the request.
* `ActiveRecord::ConnectionAdapters::ConnectionManagement` cleans active connections after each request, unless the `rack.test` key in the request environment is set to `true`.
@@ -276,6 +272,8 @@ config.middleware.delete ActionDispatch::BestStandardsSupport
* `config.active_record.auto_explain_threshold_in_seconds` configures the threshold for automatic EXPLAINs (`nil` disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
+* +config.active_record.cache_timestamp_format+ controls the format of the timestamp value in the cache key. Default is +:number+.
+
The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default.
@@ -429,11 +427,6 @@ There are a number of settings available on `config.action_mailer`:
config.action_mailer.interceptors = ["MailInterceptor"]
```
-* `config.action_mailer.queue` registers the queue that will be used to deliver the mail.
-```ruby
-config.action_mailer.queue = SomeQueue.new
-```
-
### Configuring Active Support
There are a few configuration options available in Active Support:
@@ -444,7 +437,7 @@ There are a few configuration options available in Active Support:
* `config.active_support.use_standard_json_time_format` enables or disables serializing dates to ISO 8601 format. Defaults to `true`.
-* `ActiveSupport::BufferedLogger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
+* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
* `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations.
@@ -614,7 +607,7 @@ Rails.application.config.before_initialize do
end
```
-WARNING: Some parts of your application, notably observers and routing, are not yet set up at the point where the `after_initialize` block is called.
+WARNING: Some parts of your application, notably routing, are not yet set up at the point where the `after_initialize` block is called.
### `Rails::Railtie#initializer`
@@ -644,7 +637,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde
* `load_active_support` Requires `active_support/dependencies` which sets up the basis for Active Support. Optionally requires `active_support/all` if `config.active_support.bare` is un-truthful, which is the default.
-* `initialize_logger` Initializes the logger (an `ActiveSupport::BufferedLogger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`.
+* `initialize_logger` Initializes the logger (an `ActiveSupport::Logger` object) for the application and makes it accessible at `Rails.logger`, provided that no initializer inserted before this point has defined `Rails.logger`.
* `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack.
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index feb32eb06f..7c5a472971 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -1,13 +1,15 @@
Contributing to Ruby on Rails
=============================
-This guide covers ways in which _you_ can become a part of the ongoing development of Ruby on Rails. After reading it, you should be familiar with:
+This guide covers ways in which _you_ can become a part of the ongoing development of Ruby on Rails.
-* Using GitHub to report issues
-* Cloning master and running the test suite
-* Helping to resolve existing issues
-* Contributing to the Ruby on Rails documentation
-* Contributing to the Ruby on Rails code
+After reading this guide, you will know:
+
+* How to use GitHub to report issues.
+* How to clone master and run the test suite.
+* How to help resolve existing issues.
+* How to contribute to the Ruby on Rails documentation.
+* How to contribute to the Ruby on Rails code.
Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation — all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches.
@@ -89,7 +91,7 @@ You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` als
The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
-As of this writing (December, 2010) they are specially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
+As of this writing (December, 2010) they are especially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
```bash
$ RUBYOPT=-W0 bundle exec rake test
@@ -203,7 +205,7 @@ TIP: Changes that are cosmetic in nature and do not add anything substantial to
### Follow the Coding Conventions
-Rails follows a simple set of coding style conventions.
+Rails follows a simple set of coding style conventions:
* Two spaces, no tabs (for indentation).
* No trailing whitespace. Blank lines should not have any spaces.
@@ -212,7 +214,8 @@ Rails follows a simple set of coding style conventions.
* Prefer `&&`/`||` over `and`/`or`.
* Prefer class << self over self.method for class methods.
* Use `MyClass.my_method(my_arg)` not `my_method( my_arg )` or `my_method my_arg`.
-* Use a = b and not a=b.
+* Use `a = b` and not `a=b`.
+* Use assert_not methods instead of refute.
* Follow the conventions in the source you see used already.
The above are guidelines — please use your best judgment in using them.
@@ -401,7 +404,7 @@ following:
```bash
$ git fetch upstream
-$ git checkout my_pull_request
+$ git checkout my_pull_request
$ git rebase upstream/master
$ git rebase -i
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index d4415d9b76..524fe46408 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -1,12 +1,14 @@
Debugging Rails Applications
============================
-This guide introduces techniques for debugging Ruby on Rails applications. By referring to this guide, you will be able to:
+This guide introduces techniques for debugging Ruby on Rails applications.
-* Understand the purpose of debugging
-* Track down problems and issues in your application that your tests aren't identifying
-* Learn the different ways of debugging
-* Analyze the stack trace
+After reading this guide, you will know:
+
+* The purpose of debugging.
+* How to track down problems and issues in your application that your tests aren't identifying.
+* The different ways of debugging.
+* How to analyze the stack trace.
--------------------------------------------------------------------------------
@@ -27,7 +29,7 @@ The `debug` helper will return a \<pre>-tag that renders the object using the YA
<%= debug @post %>
<p>
<b>Title:</b>
- <%=h @post.title %>
+ <%= @post.title %>
</p>
```
@@ -56,7 +58,7 @@ Displaying an instance variable, or any other object or method, in YAML format c
<%= simple_format @post.to_yaml %>
<p>
<b>Title:</b>
- <%=h @post.title %>
+ <%= @post.title %>
</p>
```
@@ -86,7 +88,7 @@ Another useful method for displaying object values is `inspect`, especially when
<%= [1, 2, 3, 4, 5].inspect %>
<p>
<b>Title:</b>
- <%=h @post.title %>
+ <%= @post.title %>
</p>
```
@@ -105,7 +107,7 @@ It can also be useful to save information to log files at runtime. Rails maintai
### What is the Logger?
-Rails makes use of the `ActiveSupport::BufferedLogger` class to write log information. You can also substitute another logger such as `Log4r` if you wish.
+Rails makes use of the `ActiveSupport::Logger` class to write log information. You can also substitute another logger such as `Log4r` if you wish.
You can specify an alternative logger in your `environment.rb` or any environment file:
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 7dfb39fb81..db43d62fcf 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -3,6 +3,8 @@ Development Dependencies Install
This guide covers how to setup an environment for Ruby on Rails core development.
+After reading this guide, you will know:
+
--------------------------------------------------------------------------------
The Easy Way
@@ -143,6 +145,9 @@ We need first to delete `.bundle/config` because Bundler remembers in that file
In order to be able to run the test suite against MySQL you need to create a user named `rails` with privileges on the test databases:
```bash
+$ mysql -uroot -p
+
+mysql> CREATE USER 'rails'@'localhost';
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.*
to 'rails'@'localhost';
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.*
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 19425765b8..e779407fab 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -9,13 +9,21 @@
name: Models
documents:
-
+ name: Active Record Basics
+ url: active_record_basics.html
+ description: This guide will get you started with models, persistence to database and the Active Record pattern and library.
+ -
name: Rails Database Migrations
url: migrations.html
description: This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner.
-
- name: Active Record Validations and Callbacks
- url: active_record_validations_callbacks.html
- description: This guide covers how you can use Active Record validations and callbacks.
+ name: Active Record Validations
+ url: active_record_validations.html
+ description: This guide covers how you can use Active Record validations
+ -
+ name: Active Record Callbacks
+ url: active_record_callbacks.html
+ description: This guide covers how you can use Active Record callbacks.
-
name: Active Record Associations
url: association_basics.html
diff --git a/guides/source/engines.md b/guides/source/engines.md
index f9bbff1c4c..116a7e67cd 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -1,13 +1,15 @@
Getting Started with Engines
============================
-In this guide you will learn about engines and how they can be used to provide additional functionality to their host applications through a clean and very easy-to-use interface. You will learn the following things in this guide:
+In this guide you will learn about engines and how they can be used to provide additional functionality to their host applications through a clean and very easy-to-use interface.
-* What makes an engine
-* How to generate an engine
-* Building features for the engine
-* Hooking the engine into an application
-* Overriding engine functionality in the application
+After reading this guide, you will know:
+
+* What makes an engine.
+* How to generate an engine.
+* Building features for the engine.
+* Hooking the engine into an application.
+* Overriding engine functionality in the application.
--------------------------------------------------------------------------------
@@ -33,7 +35,7 @@ Finally, engines would not have been possible without the work of James Adam, Pi
Generating an engine
--------------------
-To generate an engine with Rails 3.2, you will need to run the plugin generator and pass it options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal:
+To generate an engine, you will need to run the plugin generator and pass it options as appropriate to the need. For the "blorgh" example, you will need to create a "mountable" engine, running this command in a terminal:
```bash
$ rails plugin new blorgh --mountable
@@ -699,7 +701,7 @@ This section explains how to add and/or override engine MVC functionality in the
### Overriding Models and Controllers
-Engine model and controller classes can be extended by open classing them in the main Rails application (since model and controller classes are just Ruby classes that inherit Rails specific functionality). Open classing an Engine class redefines it for use in the main applicaiton. This is usually implemented by using the decorator pattern.
+Engine model and controller classes can be extended by open classing them in the main Rails application (since model and controller classes are just Ruby classes that inherit Rails specific functionality). Open classing an Engine class redefines it for use in the main application. This is usually implemented by using the decorator pattern.
For simple class modifications use `Class#class_eval`, and for complex class modifications, consider using `ActiveSupport::Concern`.
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index fc317d4773..b7145c46dc 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,17 +1,17 @@
-Rails Form helpers
-==================
+Form Helpers
+============
-Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails deals away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
+Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of form control naming and their numerous attributes. Rails does away with these complexities by providing view helpers for generating form markup. However, since they have different use-cases, developers are required to know all the differences between similar helper methods before putting them to use.
-In this guide you will:
+After reading this guide, you will know:
-* Create search forms and similar kind of generic forms not representing any specific model in your application
-* Make model-centric forms for creation and editing of specific database records
-* Generate select boxes from multiple types of data
-* Understand the date and time helpers Rails provides
-* Learn what makes a file upload form different
-* Learn some cases of building forms to external resources
-* Find out how to build complex forms
+* How to create search forms and similar kind of generic forms not representing any specific model in your application.
+* How to make model-centric forms for creation and editing of specific database records.
+* How to generate select boxes from multiple types of data.
+* The date and time helpers Rails provides.
+* What makes a file upload form different.
+* Some cases of building forms to external resources.
+* How to build complex forms.
--------------------------------------------------------------------------------
@@ -148,7 +148,9 @@ Output:
As with `check_box_tag`, the second parameter to `radio_button_tag` is the value of the input. Because these two radio buttons share the same name (age) the user will only be able to select one, and `params[:age]` will contain either "child" or "adult".
-NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and make it easier for users to click the inputs by expanding the clickable region.
+NOTE: Always use labels for checkbox and radio buttons. They associate text with a specific option and,
+by expanding the clickable region,
+make it easier for users to click the inputs.
### Other Helpers of Interest
@@ -215,7 +217,7 @@ will produce output similar to
<input id="person_name" name="person[name]" type="text" value="Henry"/>
```
-Upon form submission the value entered by the user will be stored in `params[:person][:name]`. The `params[:person]` hash is suitable for passing to `Person.new` or, if `@person` is an instance of Person, `@person.update_attributes`. While the name of an attribute is the most common second parameter to these helpers this is not compulsory. In the example above, as long as person objects have a `name` and a `name=` method Rails will be happy.
+Upon form submission the value entered by the user will be stored in `params[:person][:name]`. The `params[:person]` hash is suitable for passing to `Person.new` or, if `@person` is an instance of Person, `@person.update`. While the name of an attribute is the most common second parameter to these helpers this is not compulsory. In the example above, as long as person objects have a `name` and a `name=` method Rails will be happy.
WARNING: You must pass the name of an instance variable, i.e. `:person` or `"person"`, not an actual instance of your model object.
@@ -458,7 +460,7 @@ As with other helpers, if you were to use the `select` helper on a form builder
<%= f.select(:city_id, ...) %>
```
-WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update_attributes`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of `attr_protected` and `attr_accessible`. For further details on this, see the [Ruby On Rails Security Guide](security.html#mass-assignment).
+WARNING: If you are using `select` (or similar helpers such as `collection_select`, `select_tag`) to set a `belongs_to` association you must pass the name of the foreign key (in the example above `city_id`), not the name of association itself. If you specify `city` instead of `city_id` Active Record will raise an error along the lines of ` ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) ` when you pass the `params` hash to `Person.new` or `update`. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly.
### Option Tags from a Collection of Arbitrary Objects
@@ -534,7 +536,7 @@ The `:prefix` option is the key used to retrieve the hash of date components fro
### Model Object Helpers
`select_date` does not work well with forms that update or create Active Record objects as Active Record expects each element of the `params` hash to correspond to one attribute.
-The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example:
+The model object helpers for dates and times submit parameters with special names; when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example:
```erb
<%= date_select :person, :birth_date %>
@@ -554,7 +556,7 @@ which results in a `params` hash like
{:person => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}}
```
-When this is passed to `Person.new` (or `update_attributes`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date.civil`.
+When this is passed to `Person.new` (or `update`), Active Record spots that these parameters should all be used to construct the `birth_date` attribute and uses the suffixed information to determine in which order it should pass these parameters to functions such as `Date.civil`.
### Common Options
@@ -594,8 +596,6 @@ The following two forms both upload a file.
<% end %>
```
-NOTE: Since Rails 3.1, forms rendered using `form_for` have their encoding set to `multipart/form-data` automatically once a `file_field` is used inside the block. Previous versions required you to set this explicitly.
-
Rails provides the usual pair of helpers: the barebones `file_field_tag` and the model oriented `file_field`. The only difference with other helpers is that you cannot set a default value for file inputs as this would have no meaning. As you would expect in the first case the uploaded file is in `params[:picture]` and in the second case in `params[:person][:picture]`.
### What Gets Uploaded
@@ -622,7 +622,7 @@ Unlike other forms making an asynchronous file upload form is not as simple as p
Customizing Form Builders
-------------------------
-As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way you can also subclass FormBuilder and add the helpers there. For example
+As mentioned previously the object yielded by `form_for` and `fields_for` is an instance of FormBuilder (or a subclass thereof). Form builders encapsulate the notion of displaying form elements for a single object. While you can of course write helpers for your forms in the usual way, you can also subclass FormBuilder and add the helpers there. For example
```erb
<%= form_for @person do |f| %>
@@ -807,7 +807,7 @@ Sometimes when you submit data to an external resource, like payment gateway, fi
<% end %>
```
-The same technique is available for the `form_for` too:
+The same technique is also available for `form_for`:
```erb
<%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
diff --git a/guides/source/generators.md b/guides/source/generators.md
index d1ba19e078..62de5a70bb 100644
--- a/guides/source/generators.md
+++ b/guides/source/generators.md
@@ -3,20 +3,18 @@ Creating and Customizing Rails Generators & Templates
Rails generators are an essential tool if you plan to improve your workflow. With this guide you will learn how to create generators and customize existing ones.
-In this guide you will:
+After reading this guide, you will know:
-* Learn how to see which generators are available in your application
-* Create a generator using templates
-* Learn how Rails searches for generators before invoking them
-* Customize your scaffold by creating new generators
-* Customize your scaffold by changing generator templates
-* Learn how to use fallbacks to avoid overwriting a huge set of generators
-* Learn how to create an application template
+* How to see which generators are available in your application.
+* How to create a generator using templates.
+* How Rails searches for generators before invoking them.
+* How to customize your scaffold by creating new generators.
+* How to customize your scaffold by changing generator templates.
+* How to use fallbacks to avoid overwriting a huge set of generators.
+* How to create an application template.
--------------------------------------------------------------------------------
-NOTE: This guide is about generators in Rails 3, previous versions are not covered.
-
First Contact
-------------
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 76556761f7..aa841d5867 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -1,10 +1,11 @@
Getting Started with Rails
==========================
-This guide covers getting up and running with Ruby on Rails. After reading it,
-you should be familiar with:
+This guide covers getting up and running with Ruby on Rails.
-* Installing Rails, creating a new Rails application, and connecting your
+After reading this guide, you will know:
+
+* How to install Rails, create a new Rails application, and connect your
application to a database.
* The general layout of a Rails application.
* The basic principles of MVC (Model, View, Controller) and RESTful design.
@@ -12,9 +13,6 @@ you should be familiar with:
--------------------------------------------------------------------------------
-WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not
-work in earlier versions of Rails.
-
Guide Assumptions
-----------------
@@ -24,7 +22,7 @@ with Rails. However, to get the most out of it, you need to have some
prerequisites installed:
* The [Ruby](http://www.ruby-lang.org/en/downloads) language version 1.9.3 or higher
-* The [RubyGems](http://rubyforge.org/frs/?group_id=126) packaging system
+* The [RubyGems](http://rubygems.org/) packaging system
* To learn more about RubyGems, please read the [RubyGems User Guide](http://docs.rubygems.org/read/book/1)
* A working installation of the [SQLite3 Database](http://www.sqlite.org)
@@ -77,11 +75,14 @@ TIP: The examples below use # and $ to denote superuser and regular user termina
### Installing Rails
-Open up a command line prompt. On a mac this is called terminal, on windows it is called command prompt. Any commands prefaced with a dollar sign `$` should be run in the command line. Verify sure you have a current version of Ruby installed:
+Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose
+"Run" from your Start menu and type 'cmd.exe'. Any commands prefaced with a
+dollar sign `$` should be run in the command line. Verify that you have a
+current version of Ruby installed:
```bash
$ ruby -v
-ruby 1.9.3p194
+ruby 1.9.3p327
```
To install Rails, use the `gem install` command provided by RubyGems:
@@ -100,11 +101,11 @@ To verify that you have everything installed correctly, you should be able to ru
$ rails --version
```
-If it says something like "Rails 3.2.8" you are ready to continue.
+If it says something like "Rails 3.2.9", you are ready to continue.
### Creating the Blog Application
-Rails comes with a number of generators that are designed to make your development life easier. One of these is the new application generator, which will provide you with the foundation of a Rails application so that you don't have to write it yourself.
+Rails comes with a number of scripts called generators that are designed to make your development life easier by creating everything that's necessary to start working on a particular task. One of these is the new application generator, which will provide you with the foundation of a fresh Rails application so that you don't have to write it yourself.
To use this generator, open a terminal, navigate to a directory where you have rights to create files, and type:
@@ -165,7 +166,7 @@ This will fire up WEBrick, a webserver built into Ruby by default. To see your a
![Welcome Aboard screenshot](images/rails_welcome.png)
-TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt cursor again. For most unix like systems including mac this will be a dollar sign `$`. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server.
+TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt cursor again. For most UNIX-like systems including Mac OS X this will be a dollar sign `$`. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server.
The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your application's environment.
@@ -214,11 +215,7 @@ Open the `app/views/welcome/index.html.erb` file in your text editor and edit it
### Setting the Application Home Page
-Now that we have made the controller and view, we need to tell Rails when we want Hello Rails! to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, however, the "Welcome Aboard" smoke test is occupying that spot.
-
-To fix this, delete the `index.html` file located inside the `public` directory of the application.
-
-You need to do this because Rails will serve any static file in the `public` directory that matches a route in preference to any dynamic content you generate from the controllers. The `index.html` file is special: it will be served if a request comes in at the root route, e.g. <http://localhost:3000>. If another request such as <http://localhost:3000/welcome> happened, a static file at `public/welcome.html` would be served first, but only if it existed.
+Now that we have made the controller and view, we need to tell Rails when we want Hello Rails! to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, "Welcome Aboard" is occupying that spot.
Next, you have to tell Rails where your actual home page is located.
@@ -232,7 +229,6 @@ Blog::Application.routes.draw do
# 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"
```
@@ -519,7 +515,7 @@ invoking the command: `rake db:migrate RAILS_ENV=production`.
### Saving data in the controller
Back in `posts_controller`, we need to change the `create` action
-to use the new `Post` model to save the data in the database. Open that file
+to use the new `Post` model to save the data in the database. Open `app/controllers/posts_controller.rb`
and change the `create` action to look like this:
```ruby
@@ -557,8 +553,8 @@ parameter, which in our case will be the id of the post. Note that this
time we had to specify the actual mapping, `posts#show` because
otherwise Rails would not know which action to render.
-As we did before, we need to add the `show` action in the
-`posts_controller` and its respective view.
+As we did before, we need to add the `show` action in
+`app/controllers/posts_controller.rb` and its respective view.
```ruby
def show
@@ -702,19 +698,6 @@ your Rails models for free, including basic database CRUD (Create, Read, Update,
Destroy) operations, data validation, as well as sophisticated search support
and the ability to relate multiple models to one another.
-Rails includes methods to help you secure some of your model fields.
-Open the `app/models/post.rb` file and edit it:
-
-```ruby
-class Post < ActiveRecord::Base
- attr_accessible :text, :title
-end
-```
-
-This change will ensure that all changes made through HTML forms can edit the content of the text and title fields.
-It will not be possible to define any other field value through forms. You can still define them by calling the `field=` method of course.
-Accessible attributes and the mass assignment problem is covered in details in the [Security guide](security.html#mass-assignment)
-
### Adding Some Validation
Rails includes methods to help you validate the data that you send to models.
@@ -722,8 +705,6 @@ Open the `app/models/post.rb` file and edit it:
```ruby
class Post < ActiveRecord::Base
- attr_accessible :text, :title
-
validates :title, presence: true,
length: { minimum: 5 }
end
@@ -901,7 +882,7 @@ And then create the `update` action in `app/controllers/posts_controller.rb`:
def update
@post = Post.find(params[:id])
- if @post.update_attributes(params[:post])
+ if @post.update(params[:post])
redirect_to action: :show, id: @post.id
else
render 'edit'
@@ -909,13 +890,13 @@ def update
end
```
-The new method, `update_attributes`, is used when you want to update a record
+The new method, `update`, is used when you want to update a record
that already exists, and it accepts a hash containing the attributes
that you want to update. As before, if there was an error updating the
post we want to show the form back to the user.
-TIP: You don't need to pass all attributes to `update_attributes`. For
-example, if you'd call `@post.update_attributes(title: 'A new title')`
+TIP: You don't need to pass all attributes to `update`. For
+example, if you'd call `@post.update(title: 'A new title')`
Rails would only update the `title` attribute, leaving all other
attributes untouched.
@@ -960,35 +941,14 @@ And here's how our app looks so far:
### Using partials to clean up duplication in views
-`partials` are what Rails uses to remove duplication in views. Here's a
-simple example:
-
-```html+erb
-# app/views/user/show.html.erb
-
-<h1><%= @user.name %></h1>
-
-<%= render 'user_details' %>
-
-# app/views/user/_user_details.html.erb
-
-<%= @user.location %>
-
-<%= @user.about_me %>
-```
-
-The `users/show` template will automatically include the content of the
-`users/_user_details` template. Note that partials are prefixed by an underscore,
-as to not be confused with regular views. However, you don't include the
-underscore when including them with the `helper` method.
+Our `edit` page looks very similar to the `new` page, in fact they
+both share the same code for displaying the form. Let's remove some duplication
+by using a view partial. By convention, partial files are prefixed by an
+underscore.
TIP: You can read more about partials in the
[Layouts and Rendering in Rails](layouts_and_rendering.html) guide.
-Our `edit` action looks very similar to the `new` action, in fact they
-both share the same code for displaying the form. Let's clean them up by
-using a partial.
-
Create a new file `app/views/posts/_form.html.erb` with the following
content:
@@ -1150,7 +1110,8 @@ together.
<td><%= post.text %></td>
<td><%= link_to 'Show', action: :show, id: post.id %></td>
<td><%= link_to 'Edit', action: :edit, id: post.id %></td>
- <td><%= link_to 'Destroy', { action: :destroy, id: post.id }, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <td><%= link_to 'Destroy', { action: :destroy, id: post.id },
+ method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
@@ -1250,12 +1211,11 @@ This command will generate four files:
| test/models/comment_test.rb | Testing harness for the comments model |
| test/fixtures/comments.yml | Sample comments for use in testing |
-First, take a look at `comment.rb`:
+First, take a look at `app/models/comment.rb`:
```ruby
class Comment < ActiveRecord::Base
belongs_to :post
- attr_accessible :body, :commenter
end
```
@@ -1312,7 +1272,7 @@ this way:
* One post can have many comments.
In fact, this is very close to the syntax that Rails uses to declare this
-association. You've already seen the line of code inside the Comment model that
+association. You've already seen the line of code inside the `Comment` model (app/models/comment.rb) that
makes each comment belong to a Post:
```ruby
@@ -1321,14 +1281,14 @@ class Comment < ActiveRecord::Base
end
```
-You'll need to edit the `post.rb` file to add the other side of the association:
+You'll need to edit `app/models/post.rb` to add the other side of the association:
```ruby
class Post < ActiveRecord::Base
+ has_many :comments
validates :title, presence: true,
length: { minimum: 5 }
-
- has_many :comments
+ [...]
end
```
@@ -1385,7 +1345,7 @@ the post show page to see their comment now listed. Due to this, our
spam comments when they arrive.
So first, we'll wire up the Post show template
-(`/app/views/posts/show.html.erb`) to let us make a new comment:
+(`app/views/posts/show.html.erb`) to let us make a new comment:
```html+erb
<p>
@@ -1428,7 +1388,7 @@ class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
- redirect_to post_url(@post)
+ redirect_to post_path(@post)
end
end
```
@@ -1644,7 +1604,7 @@ So first, let's add the delete link in the
Clicking this new "Destroy Comment" link will fire off a `DELETE
/posts/:post_id/comments/:id` to our `CommentsController`, which can then use
this to find the comment we want to delete, so let's add a destroy action to our
-controller:
+controller (`app/controllers/comments_controller.rb`):
```ruby
class CommentsController < ApplicationController
@@ -1679,9 +1639,10 @@ model, `app/models/post.rb`, as follows:
```ruby
class Post < ActiveRecord::Base
+ has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
- has_many :comments, dependent: :destroy
+ [...]
end
```
@@ -1701,7 +1662,7 @@ action if that method allows it.
To use the authentication system, we specify it at the top of our
`PostsController`, in this case, we want the user to be authenticated on every
-action, except for `index` and `show`, so we write that:
+action, except for `index` and `show`, so we write that in `app/controllers/posts_controller.rb`:
```ruby
class PostsController < ApplicationController
@@ -1716,7 +1677,7 @@ class PostsController < ApplicationController
```
We also only want to allow authenticated users to delete comments, so in the
-`CommentsController` we write:
+`CommentsController` (`app/controllers/comments_controller.rb`) we write:
```ruby
class CommentsController < ApplicationController
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 5ffd955f66..2e61bea5ea 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -7,18 +7,20 @@ The process of "internationalization" usually means to abstract all strings and
So, in the process of _internationalizing_ your Rails application you have to:
-* Ensure you have support for i18n
-* Tell Rails where to find locale dictionaries
-* Tell Rails how to set, preserve and switch locales
+* Ensure you have support for i18n.
+* Tell Rails where to find locale dictionaries.
+* Tell Rails how to set, preserve and switch locales.
In the process of _localizing_ your application you'll probably want to do the following three things:
* Replace or supplement Rails' default locale — e.g. date and time formats, month names, Active Record model names, etc.
* Abstract strings in your application into keyed dictionaries — e.g. flash messages, static text in your views, etc.
-* Store the resulting dictionaries somewhere
+* Store the resulting dictionaries somewhere.
This guide will walk you through the I18n API and contains a tutorial on how to internationalize a Rails application from the start.
+After reading this guide, you will know:
+
--------------------------------------------------------------------------------
NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Rails [I18n Wiki](http://rails-i18n.org/wiki) for more information.
@@ -94,7 +96,7 @@ This means, that in the `:en` locale, the key _hello_ will map to the _Hello wor
The I18n library will use **English** as a **default locale**, i.e. if you don't set a different locale, `:en` will be used for looking up translations.
-NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize2](https://github.com/joshmh/globalize2/tree/master) may help you implement it.
+NOTE: The i18n library takes a **pragmatic approach** to locale keys (after [some discussion](http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like `:en`, `:pl`, not the _region_ part, like `:en-US` or `:en-GB`, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as `:cs`, `:th` or `:es` (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the `:en-US` locale you would have $ as a currency symbol, while in `:en-GB`, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a `:en-GB` dictionary. Various [Rails I18n plugins](http://rails-i18n.org/wiki) such as [Globalize3](https://github.com/svenfuchs/globalize3) may help you implement it.
The **translations load path** (`I18n.load_path`) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you.
@@ -132,10 +134,10 @@ However, you would probably like to **provide support for more locales** in your
WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, however **do not do this**. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being [<em>RESTful</em>](http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in [Stefan Tilkov's articles](http://www.infoq.com/articles/rest-introduction). Sometimes there are exceptions to this rule and those are discussed below.
-The _setting part_ is easy. You can set the locale in a `before_filter` in the `ApplicationController` like this:
+The _setting part_ is easy. You can set the locale in a `before_action` in the `ApplicationController` like this:
```ruby
-before_filter :set_locale
+before_action :set_locale
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
@@ -158,7 +160,7 @@ One option you have is to set the locale from the domain name where your applica
You can implement it like this in your `ApplicationController`:
```ruby
-before_filter :set_locale
+before_action :set_locale
def set_locale
I18n.locale = extract_locale_from_tld || I18n.default_locale
@@ -201,7 +203,7 @@ This solution has aforementioned advantages, however, you may not be able or may
### Setting the Locale from the URL Params
-The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_filter_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
+The most usual way of setting (and passing) the locale would be to include it in URL params, as we did in the `I18n.locale = params[:locale]` _before_action_ in the first example. We would like to have URLs like `www.example.com/books?locale=ja` or `www.example.com/ja/books` in this case.
This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though.
@@ -694,7 +696,7 @@ en:
long: "%B %d, %Y"
```
-So, all of the following equivalent lookups will return the `:short` date format `"%B %d"`:
+So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`:
```ruby
I18n.t 'date.formats.short'
diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb
index 71fe94a870..a8e4525c67 100644
--- a/guides/source/index.html.erb
+++ b/guides/source/index.html.erb
@@ -9,9 +9,7 @@ Ruby on Rails Guides
<% content_for :index_section do %>
<div id="subCol">
<dl>
- <dd class="kindle">Rails Guides are also available for Kindle and <%= link_to 'Free Kindle Reading Apps', 'http://www.amazon.com/gp/kindle/kcp' %> for the iPad,
-iPhone, Mac, Android, etc. Download them from <%= link_to 'here', @mobi %>.
- </dd>
+ <dd class="kindle">Rails Guides are also available for <%= link_to 'Kindle', @mobi %>.</dd>
<dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd>
</dl>
</div>
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 393bf51863..457e28383d 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -4,7 +4,9 @@ The Rails Initialization Process
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`
+After reading this guide, you will know:
+
+* How to use `rails server`.
--------------------------------------------------------------------------------
@@ -230,13 +232,13 @@ when 'server'
Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))
require 'rails/commands/server'
- Rails::Server.new.tap { |server|
+ Rails::Server.new.tap do |server|
# We need to require application after the server sets environment,
# otherwise the --environment option given to the server won't propagate.
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
- }
+ end
```
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.
diff --git a/guides/source/kindle/rails_guides.opf.erb b/guides/source/kindle/rails_guides.opf.erb
index 4e07664fd0..547abcbc19 100644
--- a/guides/source/kindle/rails_guides.opf.erb
+++ b/guides/source/kindle/rails_guides.opf.erb
@@ -32,7 +32,7 @@
<item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" />
- <item id="cover" media-type="image/jpeg" href="images/rails_guides_kindle_cover.jpg"/>
+ <item id="cover" media-type="image/jpg" href="images/rails_guides_kindle_cover.jpg"/>
</manifest>
<spine toc="toc">
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 141876b5a3..fa303745b8 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -3,10 +3,12 @@ Layouts and Rendering in Rails
This guide covers the basic layout features of Action Controller and Action View. By referring to this guide, you will be able to:
-* Use the various rendering methods built into Rails
-* Create layouts with multiple content sections
-* Use partials to DRY up your views
-* Use nested layouts (sub-templates)
+After reading this guide, you will know:
+
+* How to use the various rendering methods built into Rails.
+* How to create layouts with multiple content sections.
+* How to use partials to DRY up your views.
+* How to use nested layouts (sub-templates).
--------------------------------------------------------------------------------
@@ -135,7 +137,7 @@ If you want to render the view that corresponds to a different template within t
```ruby
def update
@book = Book.find(params[:id])
- if @book.update_attributes(params[:book])
+ if @book.update(params[:book])
redirect_to(@book)
else
render "edit"
@@ -143,14 +145,14 @@ def update
end
```
-If the call to `update_attributes` fails, calling the `update` action in this controller will render the `edit.html.erb` template belonging to the same controller.
+If the call to `update` fails, calling the `update` action in this controller will render the `edit.html.erb` template belonging to the same controller.
If you prefer, you can use a symbol instead of a string to specify the action to render:
```ruby
def update
@book = Book.find(params[:id])
- if @book.update_attributes(params[:book])
+ if @book.update(params[:book])
redirect_to(@book)
else
render :edit
@@ -158,21 +160,6 @@ def update
end
```
-To be explicit, you can use `render` with the `:action` option (though this is no longer necessary in Rails 3.0):
-
-```ruby
-def update
- @book = Book.find(params[:id])
- if @book.update_attributes(params[:book])
- redirect_to(@book)
- else
- render action: "edit"
- end
-end
-```
-
-WARNING: Using `render` with `:action` is a frequent source of confusion for Rails newcomers. The specified action is used to determine which view to render, but Rails does _not_ run any of the code for that action in the controller. Any instance variables that you require in the view must be set up in the current action before calling `render`.
-
#### Rendering an Action's Template from Another Controller
What if you want to render a template from an entirely different controller from the one that contains the action code? You can also do that with `render`, which accepts the full path (relative to `app/views`) of the template to render. For example, if you're running code in an `AdminProductsController` that lives in `app/controllers/admin`, you can render the results of an action to a template in `app/views/products` this way:
@@ -672,7 +659,7 @@ There are three tag options available for the `auto_discovery_link_tag`:
The `javascript_include_tag` helper returns an HTML `script` tag for each source provided.
-If you are using Rails with the [Asset Pipeline](asset_pipeline.html) enabled, this helper will generate a link to `/assets/javascripts/` rather than `public/javascripts` which was used in earlier versions of Rails. This link is then served by the Sprockets gem, which was introduced in Rails 3.1.
+If you are using Rails with the [Asset Pipeline](asset_pipeline.html) enabled, this helper will generate a link to `/assets/javascripts/` rather than `public/javascripts` which was used in earlier versions of Rails. This link is then served by the asset pipeline.
A JavaScript file within a Rails application or Rails engine goes in one of three locations: `app/assets`, `lib/assets` or `vendor/assets`. These locations are explained in detail in the [Asset Organization section in the Asset Pipeline Guide](asset_pipeline.html#asset-organization)
@@ -795,7 +782,7 @@ To include `app/assets/stylesheets/main.css` and `app/assets/stylesheets/columns
To include `app/assets/stylesheets/main.css` and `app/assets/stylesheets/photos/columns.css`:
```erb
-<%= stylesheet_link_tag "main", "/photos/columns" %>
+<%= stylesheet_link_tag "main", "photos/columns" %>
```
To include `http://example.com/main.css`:
@@ -841,7 +828,7 @@ You can even use dynamic paths such as `cache/#{current_site}/main/display`.
The `image_tag` helper builds an HTML `<img />` tag to the specified file. By default, files are loaded from `public/images`.
-WARNING: Note that you must specify the extension of the image. Previous versions of Rails would allow you to just use the image name and would append `.png` if no extension was given but Rails 3.0 does not.
+WARNING: Note that you must specify the extension of the image.
```erb
<%= image_tag "header.png" %>
@@ -1089,8 +1076,6 @@ Every partial also has a local variable with the same name as the partial (minus
Within the `customer` partial, the `customer` variable will refer to `@new_customer` from the parent view.
-WARNING: In previous versions of Rails, the default local variable would look for an instance variable with the same name as the partial in the parent. This behavior was deprecated in 2.3 and has been removed in Rails 3.0.
-
If you have an instance of a model to render into a partial, you can use a shorthand syntax:
```erb
@@ -1118,7 +1103,7 @@ Partials are very useful in rendering collections. When you pass a collection to
When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is `_product`, and within the `_product` partial, you can refer to `product` to get the instance that is being rendered.
-In Rails 3.0, there is also a shorthand for this. Assuming `@products` is a collection of `product` instances, you can simply write this in the `index.html.erb` to produce the same result:
+There is also a shorthand for this. Assuming `@products` is a collection of `product` instances, you can simply write this in the `index.html.erb` to produce the same result:
```html+erb
<h1>Products</h1>
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index a1131f1f79..617e01bd15 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -1,41 +1,39 @@
-Migrations
-==========
+Active Record Migrations
+========================
-Migrations are a convenient way for you to alter your database in a structured
-and organized manner. You could edit fragments of SQL by hand but you would then
-be responsible for telling other developers that they need to go and run them.
-You'd also have to keep track of which changes need to be run against the
-production machines next time you deploy.
+Migrations are a feature of Active Record that allows you to evolve your
+database schema over time. Rather than write schema modifications in pure SQL,
+migrations allow you to use an easy Ruby DSL to describe changes to your
+tables.
-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. Active Record will also update your `db/schema.rb` file to match the up-to-date structure of your database.
+After reading this guide, you will know:
-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
-is database independent: you don't need to worry about the precise syntax of
-`CREATE TABLE` any more than you worry about variations on `SELECT *` (you can
-drop down to raw SQL for database specific features). For example, you could use
-SQLite3 in development, but MySQL in production.
+* The generators you can use to create them.
+* The methods Active Record provides to manipulate your database.
+* The Rake tasks that manipulate migrations and your schema.
+* How migrations relate to `schema.rb`.
-In this guide, you'll learn all about migrations including:
+--------------------------------------------------------------------------------
-* The generators you can use to create them
-* The methods Active Record provides to manipulate your database
-* The Rake tasks that manipulate them
-* How they relate to `schema.rb`
+Migration Overview
+------------------
---------------------------------------------------------------------------------
+Migrations are a convenient way to alter your database schema over time in a
+consistent and easy way. They use a Ruby DSL so that you don't have to write
+SQL by hand, allowing your schema and changes to be database independent.
-Anatomy of a Migration
-----------------------
+You can think of each migration as being a new 'version' of the database. A
+schema starts off with nothing in it, and each migration modifies it to add or
+remove tables, columns, or entries. Active Record knows how to update your
+schema along this timeline, bringing it from whatever point it is in the
+history to the latest version. Active Record will also update your
+`db/schema.rb` file to match the up-to-date structure of your database.
-Before we dive into the details of a migration, here are a few examples of the
-sorts of things you can do:
+Here's an example of a migration:
```ruby
class CreateProducts < ActiveRecord::Migration
- def up
+ def change
create_table :products do |t|
t.string :name
t.text :description
@@ -43,102 +41,64 @@ class CreateProducts < ActiveRecord::Migration
t.timestamps
end
end
-
- def down
- drop_table :products
- end
end
```
-This migration adds a table called `products` with a string column called `name`
-and a text column called `description`. A primary key column called `id` will
-also be added, however since this is the default we do not need to explicitly specify it.
-The timestamp columns `created_at` and `updated_at` which Active Record
-populates automatically will also be added. Reversing this migration is as
-simple as dropping the table.
+This migration adds a table called `products` with a string column called
+`name` and a text column called `description`. A primary key column called `id`
+will also be added implicitly, as it's the default primary key for all Active
+Record models. The `timestamps` macro adds two columns, `created_at` and
+`updated_at`. These special columns are automatically managed by Active Record
+if they exist.
+
+Note that we define the change that we want to happen moving forward in time.
+Before this migration is run, there will be no table. After, the table will
+exist. Active Record knows how to reverse this migration as well: if we roll
+this migration back, it will remove the table.
+
+On databases that support transactions with statements that change the schema,
+migrations are wrapped in a transaction. If the database does not support this
+then when a migration fails the parts of it that succeeded will not be rolled
+back. You will have to rollback the changes that were made by hand.
-Migrations are not limited to changing the schema. You can also use them to fix
-bad data in the database or populate new fields:
+If you wish for a migration to do something that Active Record doesn't know how
+to reverse, you can use `reversible`:
```ruby
-class AddReceiveNewsletterToUsers < ActiveRecord::Migration
- def up
- change_table :users do |t|
- t.boolean :receive_newsletter, default: false
+class ChangeProductsPrice < ActiveRecord::Migration
+ def change
+ reversible do |dir|
+ change_table :products do |t|
+ dir.up { t.change :price, :string }
+ dir.down { t.change :price, :integer }
+ end
end
- User.update_all receive_newsletter: true
- end
-
- def down
- remove_column :users, :receive_newsletter
end
end
```
-NOTE: Some [caveats](#using-models-in-your-migrations) apply to using models in
-your migrations.
-
-This migration adds a `receive_newsletter` column to the `users` table. We want
-it to default to `false` for new users, but existing users are considered to
-have already opted in, so we use the User model to set the flag to `true` for
-existing users.
-
-### Using the change method
-
-Rails 3.1 and up makes migrations smarter by providing a `change` method.
-This method is preferred for writing constructive migrations (adding columns or
-tables). The migration knows how to migrate your database and reverse it when
-the migration is rolled back without the need to write a separate `down` method.
+Alternatively, you can use `up` and `down` instead of `change`:
```ruby
-class CreateProducts < ActiveRecord::Migration
- def change
- create_table :products do |t|
- t.string :name
- t.text :description
+class ChangeProductsPrice < ActiveRecord::Migration
+ def up
+ change_table :products do |t|
+ t.change :price, :string
+ end
+ end
- t.timestamps
+ def down
+ change_table :products do |t|
+ t.change :price, :integer
end
end
end
```
-### Migrations are Classes
-
-A migration is a subclass of `ActiveRecord::Migration` that implements
-two methods: `up` (perform the required transformations) and `down` (revert
-them).
-
-Active Record provides methods that perform common data definition tasks in a
-database independent way (you'll read about them in detail later):
-
-* `add_column`
-* `add_reference`
-* `add_index`
-* `change_column`
-* `change_table`
-* `create_table`
-* `create_join_table`
-* `drop_table`
-* `remove_column`
-* `remove_index`
-* `rename_column`
-* `remove_reference`
-
-If you need to perform tasks specific to your database (e.g., create a
-[foreign key](#active-record-and-referential-integrity) constraint) then the
-`execute` method allows you to execute arbitrary SQL. A migration is just a
-regular Ruby class so you're not limited to these functions. For example, after
-adding a column you could write code to set the value of that column for
-existing records (if necessary using your models).
-
-On databases that support transactions with statements that change the schema
-(such as PostgreSQL or SQLite3), migrations are wrapped in a transaction. If the
-database does not support this (for example MySQL) then when a migration fails
-the parts of it that succeeded will not be rolled back. You will have to rollback
-the changes that were made by hand.
+Creating a Migration
+--------------------
-### What's in a Name
+### Creating a Standalone Migration
Migrations are stored as files in the `db/migrate` directory, one for each
migration class. The name of the file is of the form
@@ -148,119 +108,12 @@ of the migration. The name of the migration class (CamelCased version)
should match the latter part of the file name. For example
`20080906120000_create_products.rb` should define class `CreateProducts` and
`20080906120001_add_details_to_products.rb` should define
-`AddDetailsToProducts`. If you do feel the need to change the file name then you
-<em>have to</em> update the name of the class inside or Rails will complain
-about a missing class.
-
-Internally Rails only uses the migration's number (the timestamp) to identify
-them. Prior to Rails 2.1 the migration number started at 1 and was incremented
-each time a migration was generated. With multiple developers it was easy for
-these to clash requiring you to rollback migrations and renumber them. With
-Rails 2.1+ this is largely avoided by using the creation time of the migration
-to identify them. You can revert to the old numbering scheme by adding the
-following line to `config/application.rb`.
-
-```ruby
-config.active_record.timestamped_migrations = false
-```
-
-The combination of timestamps and recording which migrations have been run
-allows Rails to handle common situations that occur with multiple developers.
-
-For example, Alice adds migrations `20080906120000` and `20080906123000` and Bob
-adds `20080906124500` and runs it. Alice finishes her changes and checks in her
-migrations and Bob pulls down the latest changes. When Bob runs `rake db:migrate`,
-Rails knows that it has not run Alice's two migrations so it executes the `up` method for each migration.
-
-Of course this is no substitution for communication within the team. For
-example, if Alice's migration removed a table that Bob's migration assumed to
-exist, then trouble would certainly strike.
-
-### Changing Migrations
+`AddDetailsToProducts`. Rails uses this timestamp to determine which migration
+should be run and in what order, so if you're copying a migration from another
+application or generate a file yourself, be aware of its position in the order.
-Occasionally you will make a mistake when writing a migration. If you have
-already run the migration then you cannot just edit the migration and run the
-migration again: Rails thinks it has already run the migration and so will do
-nothing when you run `rake db:migrate`. You must rollback the migration (for
-example with `rake db:rollback`), edit your migration and then run `rake db:migrate` to run the corrected version.
-
-In general, editing existing migrations is not a good idea. You will be creating
-extra work for yourself and your co-workers and cause major headaches if the
-existing version of the migration has already been run on production machines.
-Instead, you should write a new migration that performs the changes you require.
-Editing a freshly generated migration that has not yet been committed to source
-control (or, more generally, which has not been propagated beyond your
-development machine) is relatively harmless.
-
-### Supported Types
-
-Active Record supports the following database column types:
-
-* `:binary`
-* `:boolean`
-* `:date`
-* `:datetime`
-* `:decimal`
-* `:float`
-* `:integer`
-* `:primary_key`
-* `:string`
-* `:text`
-* `:time`
-* `:timestamp`
-
-These will be mapped onto an appropriate underlying database type. For example,
-with MySQL the type `:string` is mapped to `VARCHAR(255)`. You can create
-columns of types not supported by Active Record when using the non-sexy syntax such as
-
-```ruby
-create_table :products do |t|
- t.column :name, 'polygon', null: false
-end
-```
-
-This may however hinder portability to other databases.
-
-Creating a Migration
---------------------
-
-### Creating a Model
-
-The model and scaffold generators will create migrations appropriate for adding
-a new model. This migration will already contain instructions for creating the
-relevant table. If you tell Rails what columns you want, then statements for
-adding these columns will also be created. For example, running
-
-```bash
-$ rails generate model Product name:string description:text
-```
-
-TIP: All lines starting with a dollar sign `$` are intended to be run on the command line.
-
-will create a migration that looks like this
-
-```ruby
-class CreateProducts < ActiveRecord::Migration
- def change
- create_table :products do |t|
- t.string :name
- t.text :description
-
- t.timestamps
- end
- end
-end
-```
-
-You can append as many column name/type pairs as you want. By default, the
-generated migration will include `t.timestamps` (which creates the
-`updated_at` and `created_at` columns that are automatically populated
-by Active Record).
-
-### Creating a Standalone Migration
-
-If you are creating migrations for other purposes (e.g., to add a column
-to an existing table) then you can also use the migration generator:
+Of course, calculating timestamps is no fun, so Active Record provides a
+generator to handle making it for you:
```bash
$ rails generate migration AddPartNumberToProducts
@@ -303,12 +156,8 @@ generates
```ruby
class RemovePartNumberFromProducts < ActiveRecord::Migration
- def up
- remove_column :products, :part_number
- end
-
- def down
- add_column :products, :part_number, :string
+ def change
+ remove_column :products, :part_number, :string
end
end
```
@@ -334,11 +183,8 @@ As always, what has been generated for you is just a starting point. You can add
or remove from it as you see fit by editing the
`db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file.
-NOTE: The generated migration file for destructive migrations will still be
-old-style using the `up` and `down` methods. This is because Rails needs to know
-the original data types defined when you made the original changes.
-
-Also, the generator accepts column type as `references`(also available as `belongs_to`). For instance
+Also, the generator accepts column type as `references`(also available as
+`belongs_to`). For instance
```bash
$ rails generate migration AddUserRefToProducts user:references
@@ -354,12 +200,59 @@ class AddUserRefToProducts < ActiveRecord::Migration
end
```
-This migration will create a user_id column and appropriate index.
+This migration will create a `user_id` column and appropriate index.
+
+There is also a generator which will produce join tables if `JoinTable` is part of the name:
+
+```bash
+rails g migration CreateJoinTableCustomerProduct customer product
+```
+
+will produce the following migration:
+
+```ruby
+class CreateJoinTableCustomerProduct < ActiveRecord::Migration
+ def change
+ create_join_table :customers, :products do |t|
+ # t.index [:customer_id, :product_id]
+ # t.index [:product_id, :customer_id]
+ end
+ end
+end
+```
+
+### Model Generators
+
+The model and scaffold generators will create migrations appropriate for adding
+a new model. This migration will already contain instructions for creating the
+relevant table. If you tell Rails what columns you want, then statements for
+adding these columns will also be created. For example, running
+
+```bash
+$ rails generate model Product name:string description:text
+```
+
+will create a migration that looks like this
+
+```ruby
+class CreateProducts < ActiveRecord::Migration
+ def change
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+
+ t.timestamps
+ end
+ end
+end
+```
+
+You can append as many column name/type pairs as you want.
### Supported Type Modifiers
-You can also specify some options just after the field type between curly braces. You can use the
-following modifiers:
+You can also specify some options just after the field type between curly
+braces. You can use the following modifiers:
* `limit` Sets the maximum size of the `string/text/binary/integer` fields
* `precision` Defines the precision for the `decimal` fields
@@ -391,8 +284,9 @@ get to work!
### Creating a Table
-Migration method `create_table` will be one of your workhorses. A typical use
-would be
+The `create_table` method is one of the most fundamental, but most of the time,
+will be generated for you from using a model or scaffold generator. A typical
+use would be
```ruby
create_table :products do |t|
@@ -403,31 +297,11 @@ end
which creates a `products` table with a column called `name` (and as discussed
below, an implicit `id` column).
-The object yielded to the block allows you to create columns on the table. There
-are two ways of doing it. The first (traditional) form looks like
-
-```ruby
-create_table :products do |t|
- t.column :name, :string, null: false
-end
-```
-
-The second form, the so called "sexy" migration, drops the somewhat redundant
-`column` method. Instead, the `string`, `integer`, etc. methods create a column
-of that type. Subsequent parameters are the same.
-
-```ruby
-create_table :products do |t|
- t.string :name, null: false
-end
-```
-
By default, `create_table` will create a primary key called `id`. You can change
the name of the primary key with the `:primary_key` option (don't forget to
-update the corresponding model) or, if you don't want a primary key at all (for
-example for a HABTM join table), you can pass the option `id: false`. If you
-need to pass database specific options you can place an SQL fragment in the
-`:options` option. For example,
+update the corresponding model) or, if you don't want a primary key at all, you
+can pass the option `id: false`. If you need to pass database specific options
+you can place an SQL fragment in the `:options` option. For example,
```ruby
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
@@ -447,10 +321,12 @@ would be
create_join_table :products, :categories
```
-which creates a `categories_products` table with two columns called `category_id` and `product_id`.
-These columns have the option `:null` set to `false` by default.
+which creates a `categories_products` table with two columns called
+`category_id` and `product_id`. These columns have the option `:null` set to
+`false` by default.
-You can pass the option `:table_name` with you want to customize the table name. For example,
+You can pass the option `:table_name` with you want to customize the table
+name. For example,
```ruby
create_join_table :products, :categories, table_name: :categorization
@@ -458,20 +334,21 @@ create_join_table :products, :categories, table_name: :categorization
will create a `categorization` table.
-By default, `create_join_table` will create two columns with no options, but you can specify these
-options using the `:column_options` option. For example,
+By default, `create_join_table` will create two columns with no options, but
+you can specify these options using the `:column_options` option. For example,
```ruby
create_join_table :products, :categories, column_options: {null: true}
```
-will create the `product_id` and `category_id` with the `:null` option as `true`.
+will create the `product_id` and `category_id` with the `:null` option as
+`true`.
### Changing Tables
A close cousin of `create_table` is `change_table`, used for changing existing
-tables. It is used in a similar fashion to `create_table` but the object yielded
-to the block knows more tricks. For example
+tables. It is used in a similar fashion to `create_table` but the object
+yielded to the block knows more tricks. For example
```ruby
change_table :products do |t|
@@ -485,71 +362,19 @@ end
removes the `description` and `name` columns, creates a `part_number` string
column and adds an index on it. Finally it renames the `upccode` column.
-### Special Helpers
-
-Active Record provides some shortcuts for common functionality. It is for
-example very common to add both the `created_at` and `updated_at` columns and so
-there is a method that does exactly that:
-
-```ruby
-create_table :products do |t|
- t.timestamps
-end
-```
-
-will create a new products table with those two columns (plus the `id` column)
-whereas
-
-```ruby
-change_table :products do |t|
- t.timestamps
-end
-```
-adds those columns to an existing table.
-
-Another helper is called `references` (also available as `belongs_to`). In its
-simplest form it just adds some readability.
-
-```ruby
-create_table :products do |t|
- t.references :category
-end
-```
-
-will create a `category_id` column of the appropriate type. Note that you pass
-the model name, not the column name. Active Record adds the `_id` for you. If
-you have polymorphic `belongs_to` associations then `references` will add both
-of the columns required:
-
-```ruby
-create_table :products do |t|
- t.references :attachment, polymorphic: {default: 'Photo'}
-end
-```
+### When Helpers aren't Enough
-will add an `attachment_id` column and a string `attachment_type` column with
-a default value of 'Photo'. `references` also allows you to define an
-index directly, instead of using `add_index` after the `create_table` call:
+If the helpers provided by Active Record aren't enough you can use the `execute`
+method to execute arbitrary SQL:
```ruby
-create_table :products do |t|
- t.references :category, index: true
-end
+Products.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1')
```
-will create an index identical to calling `add_index :products, :category_id`.
-
-NOTE: The `references` helper does not actually create foreign key constraints
-for you. You will need to use `execute` or a plugin that adds [foreign key
-support](#active-record-and-referential-integrity).
-
-If the helpers provided by Active Record aren't enough you can use the `execute`
-method to execute arbitrary SQL.
-
For more details and examples of individual methods, check the API documentation.
In particular the documentation for
[`ActiveRecord::ConnectionAdapters::SchemaStatements`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html)
-(which provides the methods available in the `up` and `down` methods),
+(which provides the methods available in the `change`, `up` and `down` methods),
[`ActiveRecord::ConnectionAdapters::TableDefinition`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html)
(which provides the methods available on the object yielded by `create_table`)
and
@@ -558,30 +383,89 @@ and
### Using the `change` Method
-The `change` method removes the need to write both `up` and `down` methods in
-those cases that Rails knows how to revert the changes automatically. Currently,
-the `change` method supports only these migration definitions:
+The `change` method is the primary way of writing migrations. It works for the
+majority of cases, where Active Record knows how to reverse the migration
+automatically. Currently, the `change` method supports only these migration
+definitions:
* `add_column`
* `add_index`
+* `add_reference`
* `add_timestamps`
* `create_table`
+* `create_join_table`
+* `drop_table` (must supply a block)
+* `drop_join_table` (must supply a block)
* `remove_timestamps`
* `rename_column`
* `rename_index`
+* `remove_reference`
* `rename_table`
-If you're going to need to use any other methods, you'll have to write the
-`up` and `down` methods instead of using the `change` method.
+`change_table` is also reversible, as long as the block does not call `change`,
+`change_default` or `remove`.
+
+If you're going to need to use any other methods, you should use `reversible`
+or write the `up` and `down` methods instead of using the `change` method.
+
+### Using `reversible`
+
+Complex migrations may require processing that Active Record doesn't know how
+to reverse. You can use `reversible` to specify what to do when running a
+migration what else to do when reverting it. For example,
+
+```ruby
+class ExampleMigration < ActiveRecord::Migration
+ def change
+ create_table :products do |t|
+ t.references :category
+ end
+
+ reversible do |dir|
+ dir.up do
+ #add a foreign key
+ execute <<-SQL
+ ALTER TABLE products
+ ADD CONSTRAINT fk_products_categories
+ FOREIGN KEY (category_id)
+ REFERENCES categories(id)
+ SQL
+ end
+ dir.down do
+ execute <<-SQL
+ ALTER TABLE products
+ DROP FOREIGN KEY fk_products_categories
+ SQL
+ end
+ end
+
+ add_column :users, :home_page_url, :string
+ rename_column :users, :email, :email_address
+ end
+```
+
+Using `reversible` will insure that the instructions are executed in the
+right order too. If the previous example migration is reverted,
+the `down` block will be run after the `home_page_url` column is removed and
+right before the table `products` is dropped.
+
+Sometimes your migration will do something which is just plain irreversible; for
+example, it might destroy some data. In such cases, you can raise
+`ActiveRecord::IrreversibleMigration` in your `down` block. If someone tries
+to revert your migration, an error message will be displayed saying that it
+can't be done.
### Using the `up`/`down` Methods
-The `down` method of your migration should revert the transformations done by
-the `up` method. In other words, the database schema should be unchanged if you
-do an `up` followed by a `down`. For example, if you create a table in the `up`
-method, you should drop it in the `down` method. It is wise to reverse the
-transformations in precisely the reverse order they were made in the `up`
-method. For example,
+You can also use the old style of migration using `up` and `down` methods
+instead of the `change` method.
+The `up` method should describe the transformation you'd like to make to your
+schema, and the `down` method of your migration should revert the
+transformations done by the `up` method. In other words, the database schema
+should be unchanged if you do an `up` followed by a `down`. For example, if you
+create a table in the `up` method, you should drop it in the `down` method. It
+is wise to reverse the transformations in precisely the reverse order they were
+made in the `up` method. The example in the `reversible` section is equivalent to:
```ruby
class ExampleMigration < ActiveRecord::Migration
@@ -589,6 +473,7 @@ class ExampleMigration < ActiveRecord::Migration
create_table :products do |t|
t.references :category
end
+
#add a foreign key
execute <<-SQL
ALTER TABLE products
@@ -596,6 +481,7 @@ class ExampleMigration < ActiveRecord::Migration
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
+
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
@@ -603,38 +489,112 @@ class ExampleMigration < ActiveRecord::Migration
def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
+
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
+
drop_table :products
end
end
```
-Sometimes your migration will do something which is just plain irreversible; for
-example, it might destroy some data. In such cases, you can raise
+If your migration is irreversible, you should raise
`ActiveRecord::IrreversibleMigration` from your `down` method. If someone tries
to revert your migration, an error message will be displayed saying that it
can't be done.
+### Reverting Previous Migrations
+
+You can use Active Record's ability to rollback migrations using the `revert` method:
+
+```ruby
+require_relative '2012121212_example_migration'
+
+class FixupExampleMigration < ActiveRecord::Migration
+ def change
+ revert ExampleMigration
+
+ create_table(:apples) do |t|
+ t.string :variety
+ end
+ end
+end
+```
+
+The `revert` method also accepts a block of instructions to reverse.
+This could be useful to revert selected parts of previous migrations.
+For example, let's imagine that `ExampleMigration` is committed and it
+is later decided it would be best to serialize the product list instead.
+One could write:
+
+```ruby
+class SerializeProductListMigration < ActiveRecord::Migration
+ def change
+ add_column :categories, :product_list
+
+ reversible do |dir|
+ dir.up do
+ # transfer data from Products to Category#product_list
+ end
+ dir.down do
+ # create Products from Category#product_list
+ end
+ end
+
+ revert do
+ # copy-pasted code from ExampleMigration
+ create_table :products do |t|
+ t.references :category
+ end
+
+ reversible do |dir|
+ dir.up do
+ #add a foreign key
+ execute <<-SQL
+ ALTER TABLE products
+ ADD CONSTRAINT fk_products_categories
+ FOREIGN KEY (category_id)
+ REFERENCES categories(id)
+ SQL
+ end
+ dir.down do
+ execute <<-SQL
+ ALTER TABLE products
+ DROP FOREIGN KEY fk_products_categories
+ SQL
+ end
+ end
+
+ # The rest of the migration was ok
+ end
+ end
+end
+```
+
+The same migration could also have been written without using `revert`
+but this would have involved a few more steps: reversing the order
+of `create_table` and `reversible`, replacing `create_table`
+by `drop_table`, and finally replacing `up` by `down` and vice-versa.
+This is all taken care of by `revert`.
+
Running Migrations
------------------
-Rails provides a set of rake tasks to work with migrations which boil down to
-running certain sets of migrations.
+Rails provides a set of Rake tasks to run certain sets of migrations.
-The very first migration related rake task you will use will probably be
-`rake db:migrate`. In its most basic form it just runs the `up` or `change`
+The very first migration related Rake task you will use will probably be
+`rake db:migrate`. In its most basic form it just runs the `change` or `up`
method for all the migrations that have not yet been run. If there are
no such migrations, it exits. It will run these migrations in order based
on the date of the migration.
Note that running the `db:migrate` also invokes the `db:schema:dump` task, which
-will update your db/schema.rb file to match the structure of your database.
+will update your `db/schema.rb` file to match the structure of your database.
If you specify a target version, Active Record will run the required migrations
-(up, down or change) until it has reached the specified version. The version
+(change, up, down) until it has reached the specified version. The version
is the numerical prefix on the migration's filename. For example, to migrate
to version 20080906120000 run
@@ -643,7 +603,8 @@ $ rake db:migrate VERSION=20080906120000
```
If version 20080906120000 is greater than the current version (i.e., it is
-migrating upwards), this will run the `up` method on all migrations up to and
+migrating upwards), this will run the `change` (or `up`) method
+on all migrations up to and
including 20080906120000, and will not execute any later migrations. If
migrating downwards, this will run the `down` method on all the migrations
down to, but not including, 20080906120000.
@@ -658,14 +619,15 @@ number associated with the previous migration you can run
$ rake db:rollback
```
-This will run the `down` method from the latest migration. If you need to undo
+This will rollback the latest migration, either by reverting the `change`
+method or by running the `down` method. If you need to undo
several migrations you can provide a `STEP` parameter:
```bash
$ rake db:rollback STEP=3
```
-will run the `down` method from the last 3 migrations.
+will revert the last 3 migrations.
The `db:migrate:redo` task is a shortcut for doing a rollback and then migrating
back up again. As with the `db:rollback` task, you can use the `STEP` parameter
@@ -684,28 +646,33 @@ version to migrate to.
The `rake db:reset` task will drop the database, recreate it and load the
current schema into it.
-NOTE: This is not the same as running all the migrations. It will only use the contents
-of the current schema.rb file. If a migration can't be rolled back, 'rake db:reset'
-may not help you. To find out more about dumping the schema see [schema.rb](#schema-dumping-and-you).
+NOTE: This is not the same as running all the migrations. It will only use the
+contents of the current schema.rb file. If a migration can't be rolled back,
+'rake db:reset' may not help you. To find out more about dumping the schema see
+'[schema dumping and you](#schema-dumping-and-you).'
### Running Specific Migrations
If you need to run a specific migration up or down, the `db:migrate:up` and
`db:migrate:down` tasks will do that. Just specify the appropriate version and
-the corresponding migration will have its `up` or `down` method invoked, for
-example,
+the corresponding migration will have its `change`, `up` or `down` method
+invoked, for example,
```bash
$ rake db:migrate:up VERSION=20080906120000
```
-will run the `up` method from the 20080906120000 migration. This task will first
-check whether the migration is already performed and will do nothing if Active Record believes
-that it has already been run.
+will run the 20080906120000 migration by running the `change` method (or the
+`up` method). This task will
+first check whether the migration is already performed and will do nothing if
+Active Record believes that it has already been run.
### Running Migrations in Different Environments
-By default running `rake db:migrate` will run in the `development` environment. To run migrations against another environment you can specify it using the `RAILS_ENV` environment variable while running the command. For example to run migrations against the `test` environment you could run:
+By default running `rake db:migrate` will run in the `development` environment.
+To run migrations against another environment you can specify it using the
+`RAILS_ENV` environment variable while running the command. For example to run
+migrations against the `test` environment you could run:
```bash
$ rake db:migrate RAILS_ENV=test
@@ -743,9 +710,12 @@ class CreateProducts < ActiveRecord::Migration
t.timestamps
end
end
+
say "Created a table"
+
suppress_messages {add_index :products, :name}
say "and an index!", true
+
say_with_time 'Waiting for a while' do
sleep 10
250
@@ -769,11 +739,33 @@ generates the following output
If you want Active Record to not output anything, then running `rake db:migrate
VERBOSE=false` will suppress all output.
+Changing Existing Migrations
+----------------------------
+
+Occasionally you will make a mistake when writing a migration. If you have
+already run the migration then you cannot just edit the migration and run the
+migration again: Rails thinks it has already run the migration and so will do
+nothing when you run `rake db:migrate`. You must rollback the migration (for
+example with `rake db:rollback`), edit your migration and then run
+`rake db:migrate` to run the corrected version.
+
+In general, editing existing migrations is not a good idea. You will be
+creating extra work for yourself and your co-workers and cause major headaches
+if the existing version of the migration has already been run on production
+machines. Instead, you should write a new migration that performs the changes
+you require. Editing a freshly generated migration that has not yet been
+committed to source control (or, more generally, which has not been propagated
+beyond your development machine) is relatively harmless.
+
+The `revert` method can be helpful when writing a new migration to undo
+previous migrations in whole or in part
+(see [Reverting Previous Migrations](#reverting-previous-migrations) above).
+
Using Models in Your Migrations
-------------------------------
-When creating or updating data in a migration it is often tempting to use one of
-your models. After all, they exist to provide easy access to the underlying
+When creating or updating data in a migration it is often tempting to use one
+of your models. After all, they exist to provide easy access to the underlying
data. This can be done, but some caution should be observed.
For example, problems occur when the model uses database columns which are (1)
@@ -786,7 +778,7 @@ which contains a `Product` model:
Bob goes on vacation.
Alice creates a migration for the `products` table which adds a new column and
-initializes it. She also adds a validation to the `Product` model for the new
+initializes it. She also adds a validation to the `Product` model for the new
column.
```ruby
@@ -795,6 +787,9 @@ column.
class AddFlagToProduct < ActiveRecord::Migration
def change
add_column :products, :flag, :boolean
+ reversible do |dir|
+ dir.up { Product.update_all flag: false }
+ end
Product.update_all flag: false
end
end
@@ -818,7 +813,9 @@ column.
class AddFuzzToProduct < ActiveRecord::Migration
def change
add_column :products, :fuzz, :string
- Product.update_all fuzz: 'fuzzy'
+ reversible do |dir|
+ dir.up { Product.update_all fuzz: 'fuzzy' }
+ end
end
end
```
@@ -835,8 +832,8 @@ Both migrations work for Alice.
Bob comes back from vacation and:
-* Updates the source - which contains both migrations and the latest version of
- the Product model.
+* Updates the source - which contains both migrations and the latest version
+ of the Product model.
* Runs outstanding migrations with `rake db:migrate`, which
includes the one that updates the `Product` model.
@@ -851,10 +848,10 @@ An error has occurred, this and all later migrations canceled:
undefined method `fuzz' for #<Product:0x000001049b14a0>
```
-A fix for this is to create a local model within the migration. This keeps Rails
-from running the validations, so that the migrations run to completion.
+A fix for this is to create a local model within the migration. This keeps
+Rails from running the validations, so that the migrations run to completion.
-When using a faux model, it's a good idea to call
+When using a local model, it's a good idea to call
`Product.reset_column_information` to refresh the `ActiveRecord` cache for the
`Product` model prior to updating data in the database.
@@ -870,7 +867,9 @@ class AddFlagToProduct < ActiveRecord::Migration
def change
add_column :products, :flag, :boolean
Product.reset_column_information
- Product.update_all flag: false
+ reversible do |dir|
+ dir.up { Product.update_all flag: false }
+ end
end
end
```
@@ -885,7 +884,9 @@ class AddFuzzToProduct < ActiveRecord::Migration
def change
add_column :products, :fuzz, :string
Product.reset_column_information
- Product.update_all fuzz: 'fuzzy'
+ reversible do |dir|
+ dir.up { Product.update_all fuzz: 'fuzzy' }
+ end
end
end
```
@@ -893,20 +894,20 @@ end
There are other ways in which the above example could have gone badly.
For example, imagine that Alice creates a migration that selectively
-updates the +description+ field on certain products. She runs the
+updates the `description` field on certain products. She runs the
migration, commits the code, and then begins working on the next feature,
-which is to add a new column +fuzz+ to the products table.
+which is to add a new column `fuzz` to the products table.
She creates two migrations for this new feature, one which adds the new
-column, and a second which selectively updates the +fuzz+ column based on
+column, and a second which selectively updates the `fuzz` column based on
other product attributes.
These migrations run just fine, but when Bob comes back from his vacation
and calls `rake db:migrate` to run all the outstanding migrations, he gets a
-subtle bug: The descriptions have defaults, and the +fuzz+ column is present,
-but +fuzz+ is nil on all products.
+subtle bug: The descriptions have defaults, and the `fuzz` column is present,
+but `fuzz` is nil on all products.
-The solution is again to use +Product.reset_column_information+ before
+The solution is again to use `Product.reset_column_information` before
referencing the Product model in a migration, ensuring the Active Record's
knowledge of the table structure is current before manipulating data in those
records.
@@ -939,12 +940,13 @@ you desire that functionality.
### Types of Schema Dumps
-There are two ways to dump the schema. This is set in `config/application.rb` by
-the `config.active_record.schema_format` setting, which may be either `:sql` or
-`:ruby`.
+There are two ways to dump the schema. This is set in `config/application.rb`
+by the `config.active_record.schema_format` setting, which may be either `:sql`
+or `:ruby`.
If `:ruby` is selected then the schema is stored in `db/schema.rb`. If you look
-at this file you'll find that it looks an awful lot like one very big migration:
+at this file you'll find that it looks an awful lot like one very big
+migration:
```ruby
ActiveRecord::Schema.define(version: 20080906171750) do
@@ -967,8 +969,8 @@ end
In many ways this is exactly what it is. This file is created by inspecting the
database and expressing its structure using `create_table`, `add_index`, and so
on. Because this is database-independent, it could be loaded into any database
-that Active Record supports. This could be very useful if you were to distribute
-an application that is able to run against multiple databases.
+that Active Record supports. This could be very useful if you were to
+distribute an application that is able to run against multiple databases.
There is however a trade-off: `db/schema.rb` cannot express database specific
items such as foreign key constraints, triggers, or stored procedures. While in
@@ -976,15 +978,15 @@ a migration you can execute custom SQL statements, the schema dumper cannot
reconstitute those statements from the database. If you are using features like
this, then you should set the schema format to `:sql`.
-Instead of using Active Record's schema dumper, the database's structure will be
-dumped using a tool specific to the database (via the `db:structure:dump` Rake task)
-into `db/structure.sql`. For example, for the PostgreSQL RDBMS, the
-`pg_dump` utility is used. For MySQL, this file will contain the output of
-`SHOW CREATE TABLE` for the various tables.
+Instead of using Active Record's schema dumper, the database's structure will
+be dumped using a tool specific to the database (via the `db:structure:dump`
+Rake task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump`
+utility is used. For MySQL, this file will contain the output of `SHOW CREATE
+TABLE` for the various tables.
-Loading these schemas is simply a question of executing the SQL statements they
-contain. By definition, this will create a perfect copy of the database's
-structure. Using the `:sql` schema format will, however, prevent loading the
+Loading these schemas is simply a question of executing the SQL statements they
+contain. By definition, this will create a perfect copy of the database's
+structure. Using the `:sql` schema format will, however, prevent loading the
schema into a RDBMS other than the one used to create it.
### Schema Dumps and Source Control
@@ -1001,14 +1003,47 @@ which push some of that intelligence back into the database, are not heavily
used.
Validations such as `validates :foreign_key, uniqueness: true` are one way in
-which models can enforce data integrity. The `:dependent` option on associations
-allows models to automatically destroy child objects when the parent is
-destroyed. Like anything which operates at the application level, these cannot
-guarantee referential integrity and so some people augment them with foreign key
-constraints in the database.
-
-Although Active Record does not provide any tools for working directly with such
-features, the `execute` method can be used to execute arbitrary SQL. You could
-also use some plugin like [foreigner](https://github.com/matthuhiggins/foreigner)
-which add foreign key support to Active Record (including support for dumping
-foreign keys in `db/schema.rb`).
+which models can enforce data integrity. The `:dependent` option on
+associations allows models to automatically destroy child objects when the
+parent is destroyed. Like anything which operates at the application level,
+these cannot guarantee referential integrity and so some people augment them
+with foreign key constraints in the database.
+
+Although Active Record does not provide any tools for working directly with
+such features, the `execute` method can be used to execute arbitrary SQL. You
+could also use some plugin like
+[foreigner](https://github.com/matthuhiggins/foreigner) which add foreign key
+support to Active Record (including support for dumping foreign keys in
+`db/schema.rb`).
+
+Migrations and Seed Data
+------------------------
+
+Some people use migrations to add data to the database:
+
+```ruby
+class AddInitialProducts < ActiveRecord::Migration
+ def up
+ 5.times do |i|
+ Product.create(name: "Product ##{i}", description: "A product.")
+ end
+ end
+
+ def down
+ Product.delete_all
+ end
+end
+```
+
+However, Rails has a 'seeds' feature that should be used for seeding a database
+with initial data. It's a really simple feature: just fill up `db/seeds.rb`
+with some Ruby code, and run `rake db:seed`:
+
+```ruby
+5.times do |i|
+ Product.create(name: "Product ##{i}", description: "A product.")
+end
+```
+
+This is generally a much cleaner way to set up the database of a blank
+application.
diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md
index b5f112e6c9..2b46a9d51e 100644
--- a/guides/source/nested_model_forms.md
+++ b/guides/source/nested_model_forms.md
@@ -3,9 +3,9 @@ Rails nested model forms
Creating a form for a model _and_ its associations can become quite tedious. Therefore Rails provides helpers to assist in dealing with the complexities of generating these forms _and_ the required CRUD operations to create, update, and destroy associations.
-In this guide you will:
+After reading this guide, you will know:
-* do stuff
+* do stuff.
--------------------------------------------------------------------------------
diff --git a/guides/source/performance_testing.md b/guides/source/performance_testing.md
index 248a9643c8..ee0059623c 100644
--- a/guides/source/performance_testing.md
+++ b/guides/source/performance_testing.md
@@ -2,14 +2,16 @@ Performance Testing Rails Applications
======================================
This guide covers the various ways of performance testing a Ruby on Rails
-application. By referring to this guide, you will be able to:
+application.
+
+After reading this guide, you will know:
-* Understand the various types of benchmarking and profiling metrics.
-* Generate performance and benchmarking tests.
-* Install and use a GC-patched Ruby binary to measure memory usage and object
+* The various types of benchmarking and profiling metrics.
+* How to generate performance and benchmarking tests.
+* How to install and use a GC-patched Ruby binary to measure memory usage and object
allocation.
-* Understand the benchmarking information provided by Rails inside the log files.
-* Learn about various tools facilitating benchmarking and profiling.
+* The benchmarking information provided by Rails inside the log files.
+* Various tools facilitating benchmarking and profiling.
Performance testing is an integral part of the development cycle. It is very
important that you don't make your end users wait for too long before the page
@@ -413,7 +415,7 @@ tests will set the following configuration parameters:
```bash
ActionController::Base.perform_caching = true
ActiveSupport::Dependencies.mechanism = :require
-Rails.logger.level = ActiveSupport::BufferedLogger::INFO
+Rails.logger.level = ActiveSupport::Logger::INFO
```
As `ActionController::Base.perform_caching` is set to `true`, performance tests
@@ -557,9 +559,9 @@ Usage: rails profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS]
Default: 1
-o, --output PATH Directory to use when writing the results.
Default: tmp/performance
- --metrics a,b,c Metrics to use.
+ -m, --metrics a,b,c Metrics to use.
Default: process_time,memory,objects
- -m, --formats x,y,z Formats to output to.
+ -f, --formats x,y,z Formats to output to.
Default: flat,graph_html,call_tree
```
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index c657281741..f8f04c3c67 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -7,15 +7,15 @@ A Rails plugin is either an extension or a modification of the core framework. P
* a segmented architecture so that units of code can be fixed or updated on their own release schedule
* an outlet for the core developers so that they don’t have to include every cool new feature under the sun
-After reading this guide you should be familiar with:
+After reading this guide, you will know:
-* Creating a plugin from scratch
-* Writing and running tests for the plugin
+* How to create a plugin from scratch.
+* How to write and run tests for the plugin.
This guide describes how to build a test-driven plugin that will:
-* Extend core Ruby classes like Hash and String
-* Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins
+* Extend core Ruby classes like Hash and String.
+* Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins.
* Give you information about where to put generators in your plugin.
For the purpose of this guide pretend for a moment that you are an avid bird watcher.
@@ -27,16 +27,13 @@ goodness.
Setup
-----
-_"vendored plugins"_ were available in previous versions of Rails, but they are deprecated in
-Rails 3.2, and will not be available in the future.
-
Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared across
different rails applications using RubyGems and Bundler if desired.
### Generate a gemified plugin.
-Rails 3.1 ships with a `rails plugin new` command which creates a
+Rails ships with a `rails plugin new` command which creates a
skeleton for developing any kind of Rails extension with the ability
to run integration tests using a dummy Rails application. See usage
and options by asking for help:
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index 6cd19eb8e9..9e694acb98 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -3,10 +3,10 @@ Rails Application Templates
Application templates are simple Ruby files containing DSL for adding gems/initializers etc. to your freshly created Rails project or an existing Rails project.
-By referring to this guide, you will be able to:
+After reading this guide, you will know:
-* Use templates to generate/customize Rails applications
-* Write your own reusable application templates using the Rails template API
+* How to use templates to generate/customize Rails applications.
+* How to write your own reusable application templates using the Rails template API.
--------------------------------------------------------------------------------
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index afd1638ed9..a6119eb433 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -1,12 +1,14 @@
Rails on Rack
=============
-This guide covers Rails integration with Rack and interfacing with other Rack components. By referring to this guide, you will be able to:
+This guide covers Rails integration with Rack and interfacing with other Rack components.
-* Create Rails Metal applications
-* Use Rack Middlewares in your Rails applications
-* Understand Action Pack's internal Middleware stack
-* Define a custom Middleware stack
+After reading this guide, you will know:
+
+* How to create Rails Metal applications.
+* How to use Rack Middlewares in your Rails applications.
+* Action Pack's internal Middleware stack.
+* How to define a custom Middleware stack.
--------------------------------------------------------------------------------
@@ -35,11 +37,11 @@ Rails on Rack
Here's how `rails server` creates an instance of `Rack::Server`
```ruby
-Rails::Server.new.tap { |server|
+Rails::Server.new.tap do |server|
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
-}
+end
```
The `Rails::Server` inherits from `Rack::Server` and calls the `Rack::Server#start` method this way:
@@ -227,7 +229,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`Rack::Lock`**
-* Sets `env["rack.multithread"]` flag to `true` and wraps the application within a Mutex.
+* Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex.
**`ActiveSupport::Cache::Strategy::LocalCache::Middleware`**
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 53f037c25b..14f23d4020 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -1,13 +1,15 @@
Rails Routing from the Outside In
=================================
-This guide covers the user-facing features of Rails routing. By referring to this guide, you will be able to:
+This guide covers the user-facing features of Rails routing.
-* Understand the code in `routes.rb`
-* Construct your own routes, using either the preferred resourceful style or the `match` method
-* Identify what parameters to expect an action to receive
-* Automatically create paths and URLs using route helpers
-* Use advanced techniques such as constraints and Rack endpoints
+After reading this guide, you will know:
+
+* How to interpret the code in `routes.rb`.
+* How to construct your own routes, using either the preferred resourceful style or the `match` method.
+* What parameters to expect an action to receive.
+* How to automatically create paths and URLs using route helpers.
+* Advanced techniques such as constraints and Rack endpoints.
--------------------------------------------------------------------------------
@@ -18,13 +20,13 @@ The Rails router recognizes URLs and dispatches them to a controller's action. I
### Connecting URLs to Code
-When your Rails application receives an incoming request
+When your Rails application receives an incoming request for:
```
GET /patients/17
```
-it asks the router to match it to a controller action. If the first matching route is
+it asks the router to match it to a controller action. If the first matching route is:
```ruby
get '/patients/:id', to: 'patients#show'
@@ -34,23 +36,25 @@ the request is dispatched to the `patients` controller's `show` action with `{ i
### Generating Paths and URLs from Code
-You can also generate paths and URLs. If the route above is modified to be
+You can also generate paths and URLs. If the route above is modified to be:
```ruby
get '/patients/:id', to: 'patients#show', as: 'patient'
```
-If your application contains this code:
+and your application contains this code in the controller:
```ruby
@patient = Patient.find(17)
```
+and this in the corresponding view:
+
```erb
<%= link_to 'Patient Record', patient_path(@patient) %>
```
-The router will generate the path `/patients/17`. This reduces the brittleness of your view and makes your code easier to understand. Note that the id does not need to be specified in the route helper.
+then the router will generate the path `/patients/17`. This reduces the brittleness of your view and makes your code easier to understand. Note that the id does not need to be specified in the route helper.
Resource Routing: the Rails Default
-----------------------------------
@@ -61,13 +65,13 @@ Resource routing allows you to quickly declare all of the common routes for a gi
Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as `GET`, `POST`, `PATCH`, `PUT` and `DELETE`. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
-When your Rails application receives an incoming request for
+When your Rails application receives an incoming request for:
```
DELETE /photos/17
```
-it asks the router to map it to a controller action. If the first matching route is
+it asks the router to map it to a controller action. If the first matching route is:
```ruby
resources :photos
@@ -77,7 +81,7 @@ Rails would dispatch that request to the `destroy` method on the `photos` contro
### CRUD, Verbs, and Actions
-In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as
+In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as:
```ruby
resources :photos
@@ -85,7 +89,7 @@ resources :photos
creates seven different routes in your application, all mapping to the `Photos` controller:
-| HTTP Verb | Path | action | used for |
+| HTTP Verb | Path | Action | Used for |
| --------- | ---------------- | ------- | -------------------------------------------- |
| GET | /photos | index | display a list of all photos |
| GET | /photos/new | new | return an HTML form for creating a new photo |
@@ -95,9 +99,11 @@ creates seven different routes in your application, all mapping to the `Photos`
| PATCH/PUT | /photos/:id | update | update a specific photo |
| DELETE | /photos/:id | destroy | delete a specific photo |
+NOTE: Because the router uses the HTTP verb and URL to match inbound requests, four URLs map to seven different actions.
+
NOTE: Rails routes are matched in the order they are specified, so if you have a `resources :photos` above a `get 'photos/poll'` the `show` action's route for the `resources` line will be matched before the `get` line. To fix this, move the `get` line **above** the `resources` line so that it is matched first.
-### Paths and URLs
+### Path and URL Helpers
Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of `resources :photos`:
@@ -108,8 +114,6 @@ Creating a resourceful route will also expose a number of helpers to the control
Each of these helpers has a corresponding `_url` helper (such as `photos_url`) which returns the same path prefixed with the current host, port and path prefix.
-NOTE: Because the router uses the HTTP verb and URL to match inbound requests, four URLs map to seven different actions.
-
### Defining Multiple Resources at the Same Time
If you need to create routes for more than one resource, you can save a bit of typing by defining them all with a single call to `resources`:
@@ -118,7 +122,7 @@ If you need to create routes for more than one resource, you can save a bit of t
resources :photos, :books, :videos
```
-This works exactly the same as
+This works exactly the same as:
```ruby
resources :photos
@@ -128,13 +132,13 @@ resources :videos
### Singular Resources
-Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like `/profile` to always show the profile of the currently logged in user. In this case, you can use a singular resource to map `/profile` (rather than `/profile/:id`) to the `show` action.
+Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like `/profile` to always show the profile of the currently logged in user. In this case, you can use a singular resource to map `/profile` (rather than `/profile/:id`) to the `show` action:
```ruby
get 'profile', to: 'users#show'
```
-This resourceful route
+This resourceful route:
```ruby
resource :geocoder
@@ -142,7 +146,7 @@ resource :geocoder
creates six different routes in your application, all mapping to the `Geocoders` controller:
-| HTTP Verb | Path | action | used for |
+| HTTP Verb | Path | Action | Used for |
| --------- | -------------- | ------- | --------------------------------------------- |
| GET | /geocoder/new | new | return an HTML form for creating the geocoder |
| POST | /geocoder | create | create the new geocoder |
@@ -173,7 +177,7 @@ end
This will create a number of routes for each of the `posts` and `comments` controller. For `Admin::PostsController`, Rails will create:
-| HTTP Verb | Path | action | used for |
+| HTTP Verb | Path | Action | Used for |
| --------- | --------------------- | ------- | ------------------------- |
| GET | /admin/posts | index | admin_posts_path |
| GET | /admin/posts/new | new | new_admin_post_path |
@@ -183,7 +187,7 @@ This will create a number of routes for each of the `posts` and `comments` contr
| PATCH/PUT | /admin/posts/:id | update | admin_post_path(:id) |
| DELETE | /admin/posts/:id | destroy | admin_post_path(:id) |
-If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use
+If you want to route `/posts` (without the prefix `/admin`) to `Admin::PostsController`, you could use:
```ruby
scope module: 'admin' do
@@ -191,13 +195,13 @@ scope module: 'admin' do
end
```
-or, for a single case
+or, for a single case:
```ruby
resources :posts, module: 'admin'
```
-If you want to route `/admin/posts` to `PostsController` (without the `Admin::` module prefix), you could use
+If you want to route `/admin/posts` to `PostsController` (without the `Admin::` module prefix), you could use:
```ruby
scope '/admin' do
@@ -205,7 +209,7 @@ scope '/admin' do
end
```
-or, for a single case
+or, for a single case:
```ruby
resources :posts, path: '/admin/posts'
@@ -213,7 +217,7 @@ resources :posts, path: '/admin/posts'
In each of these cases, the named routes remain the same as if you did not use `scope`. In the last case, the following paths map to `PostsController`:
-| HTTP Verb | Path | action | named helper |
+| HTTP Verb | Path | Action | Named Helper |
| --------- | --------------------- | ------- | ------------------- |
| GET | /admin/posts | index | posts_path |
| GET | /admin/posts/new | new | new_post_path |
@@ -247,7 +251,7 @@ end
In addition to the routes for magazines, this declaration will also route ads to an `AdsController`. The ad URLs require a magazine:
-| HTTP Verb | Path | action | used for |
+| HTTP Verb | Path | Action | Used for |
| --------- | ------------------------------------ | ------- | -------------------------------------------------------------------------- |
| GET | /magazines/:magazine_id/ads | index | display a list of all ads for a specific magazine |
| GET | /magazines/:magazine_id/ads/new | new | return an HTML form for creating a new ad belonging to a specific magazine |
@@ -271,7 +275,7 @@ resources :publishers do
end
```
-Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize paths such as
+Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize paths such as:
```
/publishers/1/magazines/2/photos/3
@@ -281,9 +285,94 @@ The corresponding route helper would be `publisher_magazine_photo_url`, requirin
TIP: _Resources should never be nested more than 1 level deep._
+#### Shallow Nesting
+
+One way to avoid deep nesting (as recommended above) is to generate the collection actions scoped under the parent, so as to get a sense of the hierarchy, but to not nest the member actions. In other words, to only build routes with the minimal amount of information to uniquely identify the resource, like this:
+
+```ruby
+resources :posts do
+ resources :comments, only: [:index, :new, :create]
+end
+resources :comments, only: [:show, :edit, :update, :destroy]
+```
+
+This idea strikes a balance between descriptive routes and deep nesting. There exists shorthand syntax to achieve just that, via the `:shallow` option:
+
+```ruby
+resources :posts do
+ resources :comments, shallow: true
+end
+```
+
+This will generate the exact same routes as the first example. You can also specify the `:shallow` option in the parent resource, in which case all of the nested resources will be shallow:
+
+```ruby
+resources :posts, shallow: true do
+ resources :comments
+ resources :quotes
+ resources :drafts
+end
+```
+
+The `shallow` method of the DSL creates a scope inside of which every nesting is shallow. This generates the same routes as the previous example:
+
+```ruby
+shallow do
+ resources :posts do
+ resources :comments
+ resources :quotes
+ resources :drafts
+ end
+end
+```
+
+There exists two options for `scope` to customize shallow routes. `:shallow_path` prefixes member paths with the specified parameter:
+
+```ruby
+scope shallow_path: "sekret" do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+end
+```
+
+The comments resource here will have the following routes generated for it:
+
+| HTTP Verb | Path | Named Helper |
+| --------- | -------------------------------------- | ------------------- |
+| GET | /posts/:post_id/comments(.:format) | post_comments |
+| POST | /posts/:post_id/comments(.:format) | post_comments |
+| GET | /posts/:post_id/comments/new(.:format) | new_post_comment |
+| GET | /sekret/comments/:id/edit(.:format) | edit_comment |
+| GET | /sekret/comments/:id(.:format) | comment |
+| PATCH/PUT | /sekret/comments/:id(.:format) | comment |
+| DELETE | /sekret/comments/:id(.:format) | comment |
+
+The `:shallow_prefix` option adds the specified parameter to the named helpers:
+
+```ruby
+scope shallow_prefix: "sekret" do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+end
+```
+
+The comments resource here will have the following routes generated for it:
+
+| HTTP Verb | Path | Named Helper |
+| --------- | -------------------------------------- | ------------------- |
+| GET | /posts/:post_id/comments(.:format) | post_comments |
+| POST | /posts/:post_id/comments(.:format) | post_comments |
+| GET | /posts/:post_id/comments/new(.:format) | new_post_comment |
+| GET | /comments/:id/edit(.:format) | edit_sekret_comment |
+| GET | /comments/:id(.:format) | sekret_comment |
+| PATCH/PUT | /comments/:id(.:format) | sekret_comment |
+| DELETE | /comments/:id(.:format) | sekret_comment |
+
### Routing concerns
-Routing Concerns allows you to declare common routes that can be reused inside others resources and routes.
+Routing Concerns allows you to declare common routes that can be reused inside others resources and routes. To define a concern:
```ruby
concern :commentable do
@@ -295,7 +384,7 @@ concern :image_attachable do
end
```
-These concerns can be used in resources to avoid code duplication and share behavior across routes.
+These concerns can be used in resources to avoid code duplication and share behavior across routes:
```ruby
resources :messages, concerns: :commentable
@@ -303,6 +392,19 @@ resources :messages, concerns: :commentable
resources :posts, concerns: [:commentable, :image_attachable]
```
+The above is equivalent to:
+
+```ruby
+resources :messages do
+ resources :comments
+end
+
+resources :posts do
+ resources :comments
+ resources :images, only: :index
+end
+```
+
Also you can use them in any place that you want inside the routes, for example in a scope or namespace call:
```ruby
@@ -321,7 +423,7 @@ resources :magazines do
end
```
-When using `magazine_ad_path`, you can pass in instances of `Magazine` and `Ad` instead of the numeric IDs.
+When using `magazine_ad_path`, you can pass in instances of `Magazine` and `Ad` instead of the numeric IDs:
```erb
<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>
@@ -369,7 +471,7 @@ resources :photos do
end
```
-This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`. It will also create the `preview_photo_url` and `preview_photo_path` helpers.
+This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`, with the resource id value passed in `params[:id]`. It will also create the `preview_photo_url` and `preview_photo_path` helpers.
Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use `get`, `patch`, `put`, `post`, or `delete` here. If you don't have multiple `member` routes, you can also pass `:on` to a route, eliminating the block:
@@ -379,6 +481,8 @@ resources :photos do
end
```
+You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`.
+
#### Adding Collection Routes
To add a route to the collection:
@@ -413,9 +517,7 @@ end
This will enable Rails to recognize paths such as `/comments/new/preview` with GET, and route to the `preview` action of `CommentsController`. It will also create the `preview_new_comment_url` and `preview_new_comment_path` route helpers.
-#### A Note of Caution
-
-If you find yourself adding many extra actions to a resourceful route, it's time to stop and ask yourself whether you're disguising the presence of another resource.
+TIP: If you find yourself adding many extra actions to a resourceful route, it's time to stop and ask yourself whether you're disguising the presence of another resource.
Non-Resourceful Routes
----------------------
@@ -452,11 +554,11 @@ NOTE: You can't use `:namespace` or `:module` with a `:controller` path segment.
get ':controller(/:action(/:id))', controller: /admin\/[^\/]+/
```
-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.
+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.
### Static Segments
-You can specify static segments when creating a route:
+You can specify static segments when creating a route by not prepending a colon to a fragment:
```ruby
get ':controller/:action/:id/with_user/:user_id'
@@ -494,7 +596,7 @@ Rails would match `photos/12` to the `show` action of `PhotosController`, and se
### Naming Routes
-You can specify a name for any route using the `:as` option.
+You can specify a name for any route using the `:as` option:
```ruby
get 'exit', to: 'sessions#destroy', as: :logout
@@ -524,7 +626,7 @@ You can match all verbs to a particular route using `via: :all`:
match 'photos', to: 'photos#show', via: :all
```
-You should avoid routing all verbs to an action unless you have a good reason to, as routing both `GET` requests and `POST` requests to a single action has security implications.
+NOTE: Routing both `GET` and `POST` requests to a single action has security implications. In general, you should avoid routing all verbs to an action unless you have a good reason to.
### Segment Constraints
@@ -534,7 +636,7 @@ You can use the `:constraints` option to enforce a format for a dynamic segment:
get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ }
```
-This route would match paths such as `/photos/A12345`. You can more succinctly express the same route this way:
+This route would match paths such as `/photos/A12345`, but not `/photos/893`. You can more succinctly express the same route this way:
```ruby
get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/
@@ -607,17 +709,17 @@ end
Both the `matches?` method and the lambda gets the `request` object as an argument.
-### Route Globbing
+### Route Globbing and Wildcard Segments
-Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example
+Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example:
```ruby
get 'photos/*other', to: 'photos#unknown'
```
-This route would match `photos/12` or `/photos/long/path/to/12`, setting `params[:other]` to `"12"` or `"long/path/to/12"`.
+This route would match `photos/12` or `/photos/long/path/to/12`, setting `params[:other]` to `"12"` or `"long/path/to/12"`. The fragments prefixed with a star are called "wildcard segments".
-Wildcard segments can occur anywhere in a route. For example,
+Wildcard segments can occur anywhere in a route. For example:
```ruby
get 'books/*section/:title', to: 'books#show'
@@ -625,7 +727,7 @@ get 'books/*section/:title', to: 'books#show'
would match `books/some/section/last-words-a-memoir` with `params[:section]` equals `'some/section'`, and `params[:title]` equals `'last-words-a-memoir'`.
-Technically a route can have even more than one wildcard segment. The matcher assigns segments to parameters in an intuitive way. For example,
+Technically, a route can have even more than one wildcard segment. The matcher assigns segments to parameters in an intuitive way. For example:
```ruby
get '*a/foo/*b', to: 'test#index'
@@ -633,12 +735,6 @@ get '*a/foo/*b', to: 'test#index'
would match `zoo/woo/foo/bar/baz` with `params[:a]` equals `'zoo/woo'`, and `params[:b]` equals `'bar/baz'`.
-NOTE: Starting from Rails 3.1, wildcard routes will always match the optional format segment by default. For example if you have this route:
-
-```ruby
-get '*pages', to: 'pages#show'
-```
-
NOTE: By requesting `'/foo/bar.json'`, your `params[:pages]` will be equals to `'foo/bar'` with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `format: false` like this:
```ruby
@@ -678,7 +774,7 @@ In all of these cases, if you don't provide the leading host (`http://www.exampl
### Routing to Rack Applications
-Instead of a String, like `'posts#index'`, which corresponds to the `index` action in the `PostsController`, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher.
+Instead of a String like `'posts#index'`, which corresponds to the `index` action in the `PostsController`, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher:
```ruby
match '/application.js', to: Sprockets, via: :all
@@ -697,13 +793,13 @@ root to: 'pages#main'
root 'pages#main' # shortcut for the above
```
-You should put the `root` route at the top of the file, because it is the most popular route and should be matched first. You also need to delete the `public/index.html` file for the root route to take effect.
+You should put the `root` route at the top of the file, because it is the most popular route and should be matched first.
NOTE: The `root` route only routes `GET` requests to the action.
### Unicode character routes
-You can specify unicode character routes directly. For example
+You can specify unicode character routes directly. For example:
```ruby
get 'こんにちは', to: 'welcome#index'
@@ -724,7 +820,7 @@ resources :photos, controller: 'images'
will recognize incoming paths beginning with `/photos` but route to the `Images` controller:
-| HTTP Verb | Path | action | named helper |
+| HTTP Verb | Path | Action | Named Helper |
| --------- | ---------------- | ------- | -------------------- |
| GET | /photos | index | photos_path |
| GET | /photos/new | new | new_photo_path |
@@ -769,7 +865,7 @@ resources :photos, as: 'images'
will recognize incoming paths beginning with `/photos` and route the requests to `PhotosController`, but use the value of the :as option to name the helpers.
-| HTTP Verb | Path | action | named helper |
+| HTTP Verb | Path | Action | Named Helper |
| --------- | ---------------- | ------- | -------------------- |
| GET | /photos | index | images_path |
| GET | /photos/new | new | new_image_path |
@@ -787,7 +883,7 @@ The `:path_names` option lets you override the automatically-generated "new" and
resources :photos, path_names: { new: 'make', edit: 'change' }
```
-This would cause the routing to recognize paths such as
+This would cause the routing to recognize paths such as:
```
/photos/make
@@ -806,7 +902,7 @@ end
### Prefixing the Named Route Helpers
-You can use the `:as` option to prefix the named route helpers that Rails generates for a route. Use this option to prevent name collisions between routes using a path scope.
+You can use the `:as` option to prefix the named route helpers that Rails generates for a route. Use this option to prevent name collisions between routes using a path scope. For example:
```ruby
scope 'admin' do
@@ -874,7 +970,7 @@ end
Rails now creates routes to the `CategoriesController`.
-| HTTP Verb | Path | action | used for |
+| HTTP Verb | Path | Action | Used for |
| --------- | -------------------------- | ------- | ----------------------- |
| GET | /kategorien | index | categories_path |
| GET | /kategorien/neu | new | new_category_path |
@@ -886,7 +982,7 @@ Rails now creates routes to the `CategoriesController`.
### Overriding the Singular Form
-If you want to define the singular form of a resource, you should add additional rules to the `Inflector`.
+If you want to define the singular form of a resource, you should add additional rules to the `Inflector`:
```ruby
ActiveSupport::Inflector.inflections do |inflect|
@@ -896,7 +992,7 @@ end
### Using `:as` in Nested Resources
-The `:as` option overrides the automatically-generated name for the resource in nested route helpers. For example,
+The `:as` option overrides the automatically-generated name for the resource in nested route helpers. For example:
```ruby
resources :magazines do
@@ -911,7 +1007,7 @@ Inspecting and Testing Routes
Rails offers facilities for inspecting and testing your routes.
-### Seeing Existing Routes
+### Listing 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.
@@ -949,7 +1045,7 @@ Routes should be included in your testing strategy (just like the rest of your a
#### The `assert_generates` Assertion
-`assert_generates` asserts that a particular set of options generate a particular path and can be used with default routes or custom routes.
+`assert_generates` asserts that a particular set of options generate a particular path and can be used with default routes or custom routes. For example:
```ruby
assert_generates '/photos/1', { controller: 'photos', action: 'show', id: '1' }
@@ -958,7 +1054,7 @@ assert_generates '/about', controller: 'pages', action: 'about'
#### The `assert_recognizes` Assertion
-`assert_recognizes` is the inverse of `assert_generates`. It asserts that a given path is recognized and routes it to a particular spot in your application.
+`assert_recognizes` is the inverse of `assert_generates`. It asserts that a given path is recognized and routes it to a particular spot in your application. For example:
```ruby
assert_recognizes({ controller: 'photos', action: 'show', id: '1' }, '/photos/1')
@@ -972,7 +1068,7 @@ assert_recognizes({ controller: 'photos', action: 'create' }, { path: 'photos',
#### The `assert_routing` Assertion
-The `assert_routing` assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of `assert_generates` and `assert_recognizes`.
+The `assert_routing` assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of `assert_generates` and `assert_recognizes`:
```ruby
assert_routing({ path: 'photos', method: :post }, { controller: 'photos', action: 'create' })
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index e589a3d093..a78711f4b2 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -3,6 +3,11 @@ Ruby on Rails Guides Guidelines
This guide documents guidelines for writing Ruby on Rails Guides. This guide follows itself in a graceful loop, serving itself as an example.
+After reading this guide, you will know:
+
+* About the conventions to be used in Rails documentation.
+* How to generate guides locally.
+
--------------------------------------------------------------------------------
Markdown
@@ -60,7 +65,7 @@ HTML Guides
### Generation
-To generate all the guides, just `cd` into the **`guides`** directory and execute:
+To generate all the guides, just `cd` into the **`guides`** directory, run `bundle install` and execute:
```
bundle exec rake guides:generate
@@ -72,8 +77,6 @@ or
bundle exec rake guides:generate:html
```
-(You may need to run `bundle install` first to install the required gems.)
-
To process `my_guide.md` and nothing else use the `ONLY` environment variable:
```
diff --git a/guides/source/security.md b/guides/source/security.md
index 4902f83f8a..0b0cfe69c4 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1,15 +1,17 @@
Ruby On Rails Security Guide
============================
-This manual describes common security problems in web applications and how to avoid them with Rails. After reading it, you should be familiar with:
+This manual describes common security problems in web applications and how to avoid them with Rails.
-* All countermeasures _that are highlighted_
-* The concept of sessions in Rails, what to put in there and popular attack methods
-* How just visiting a site can be a security problem (with CSRF)
-* What you have to pay attention to when working with files or providing an administration interface
-* The Rails-specific mass assignment problem
-* How to manage users: Logging in and out and attack methods on all layers
-* And the most popular injection attack methods
+After reading this guide, you will know:
+
+* All countermeasures _that are highlighted_.
+* The concept of sessions in Rails, what to put in there and popular attack methods.
+* How just visiting a site can be a security problem (with CSRF).
+* What you have to pay attention to when working with files or providing an administration interface.
+* The Rails-specific mass assignment problem.
+* How to manage users: Logging in and out and attack methods on all layers.
+* And the most popular injection attack methods.
--------------------------------------------------------------------------------
@@ -92,16 +94,15 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves
* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret and inserted into the end of the cookie.
-That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA512, which has not been compromised, yet). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_. Put the secret in your environment.rb:
+That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA512, which has not been compromised, yet). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_.
-```ruby
-config.action_dispatch.session = {
- key: '_app_session',
- secret: '0x0dkfj3927dkc7djdh36rkckdfzsg...'
-}
-```
+`config.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`, e.g.:
-There are, however, derivatives of CookieStore which encrypt the session hash, so the client cannot see it.
+ YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'
+
+Older versions of Rails use CookieStore, which uses `secret_token` instead of `secret_key_base` that is used by EncryptedCookieStore. Read the upgrade documentation for more information.
+
+If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.
### Replay Attacks for CookieStore Sessions
@@ -372,141 +373,6 @@ The common admin interface works like this: it's located at www.example.com/admi
* _Put the admin interface to a special sub-domain_ such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, www.application.com, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on www.application.com may not read the cookie for admin.application.com and vice-versa.
-Mass Assignment
----------------
-
-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:
-
-```ruby
-def signup
- params[:user] # => {name:"ow3ned", admin:true}
- @user = User.new(params[:user])
-end
-```
-
-Mass-assignment saves you much work, because you don't have to set each value
-individually. Simply pass a hash to the `new` method, or `assign_attributes=`
-a hash value, to set the model's attributes to the values in the hash. The
-problem is that it is often used in conjunction with the parameters (params)
-hash available in the controller, which may be manipulated by an attacker.
-He may do so by changing the URL like this:
-
-```
-http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
-```
-
-This will set the following parameters in the controller:
-
-```ruby
-params[:user] # => {name:"ow3ned", admin:true}
-```
-
-So if you create a new user using mass-assignment, it may be too easy to become
-an administrator.
-
-Note that this vulnerability is not restricted to database columns. Any setter
-method, unless explicitly protected, is accessible via the `attributes=` method.
-In fact, this vulnerability is extended even further with the introduction of
-nested mass assignment (and nested object forms) in Rails 2.3. The
-`accepts_nested_attributes_for` declaration provides us the ability to extend
-mass assignment to model associations (`has_many`, `has_one`,
-`has_and_belongs_to_many`). For example:
-
-```ruby
- class Person < ActiveRecord::Base
- has_many :children
-
- accepts_nested_attributes_for :children
- end
-
- class Child < ActiveRecord::Base
- belongs_to :person
- end
-```
-
-As a result, the vulnerability is extended beyond simply exposing column
-assignment, allowing attackers the ability to create entirely new records
-in referenced tables (children in this case).
-
-### Countermeasures
-
-To avoid this, Rails provides an interface for protecting attributes from
-end-user assignment called Strong Parameters. This makes Action Controller
-parameters forbidden until they have been whitelisted, so you will have to
-make a conscious choice about which attributes to allow for mass assignment
-and thus prevent accidentally exposing that which shouldn’t be exposed.
-
-NOTE. Before Strong Parameters arrived, mass-assignment protection was a
-model's task provided by Active Model. This has been extracted to the
-[ProtectedAttributes](https://github.com/rails/protected_attributes)
-gem. In order to use `attr_accessible` and `attr_protected` helpers in
-your models, you should add `protected_attributes` to your Gemfile.
-
-Why we moved mass-assignment protection out of the model and into
-the controller? The whole point of the controller is to control the
-flow between user and application, including authentication, authorization,
-and, as part of that, access control.
-
-Strong Parameters provides two methods to the `params` hash to control
-access to your attributes: `require` and `permit`. The former is used
-to mark parameters as required and the latter limits which attributes
-should be allowed for mass updating using the slice pattern. For example:
-
-```ruby
-def signup
- params[:user]
- # => {name:"ow3ned", admin:true}
- permitted_params = params.require(:user).permit(:name)
- # => {name:"ow3ned"}
-
- @user = User.new(permitted_params)
-end
-```
-
-In the example above, `require` is checking whether a `user` key is present or not
-in the parameters, if it's not present, it'll raise an `ActionController::MissingParameter`
-exception, which will be caught by `ActionController::Base` and turned into a
-400 Bad Request reply. Then `permit` whitelists the attributes that should be
-allowed for mass assignment.
-
-A good pattern to encapsulate the permissible parameters is to use a private method
-since you'll be able to reuse the same permit list between different actions.
-
-```ruby
-def signup
- @user = User.new(user_params)
- # ...
-end
-
-def update
- @user = User.find(params[:id]
- @user.update_attributes!(user_params)
- # ...
-end
-
-private
- def user_params
- params.require(:user).permit(:name)
- end
-```
-
-Also, you can specialize this method with per-user checking of permissible
-attributes.
-
-```ruby
-def user_params
- if current_user.admin?
- params.require(:user).permit(:name, :admin)
- else
- params.require(:user).permit(:name)
- end
-end
-```
-
User Management
---------------
@@ -686,8 +552,7 @@ NOTE: _When sanitizing, protecting or verifying something, whitelists over black
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), _prefer to use whitelist approaches_:
-* Use before_filter only: [...] instead of except: [...]. This way you don't forget to turn it off for newly added actions.
-* Use attr_accessible instead of attr_protected. See the mass-assignment section for details
+* Use before_action only: [...] instead of except: [...]. This way you don't forget to turn it off for newly added actions.
* Allow &lt;strong&gt; instead of removing &lt;script&gt; against Cross-Site Scripting (XSS). See below for details.
* Don't try to correct user input by blacklists:
* This will make the attack work: "&lt;sc&lt;script&gt;ript&gt;".gsub("&lt;script&gt;", "")
@@ -1093,6 +958,11 @@ Used to control which sites are allowed to bypass same origin policies and send
* Strict-Transport-Security
[Used to control if the browser is allowed to only access a site over a secure connection](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)
+Environmental Security
+----------------------
+
+It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/initializers/secret_token.rb`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
+
Additional Resources
--------------------
diff --git a/guides/source/testing.md b/guides/source/testing.md
index f898456d39..7747318d32 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -2,11 +2,13 @@ A Guide to Testing Rails Applications
=====================================
This guide covers built-in mechanisms offered by Rails to test your
-application. By referring to this guide, you will be able to:
+application.
-* Understand Rails testing terminology
-* Write unit, functional, and integration tests for your application
-* Identify other popular testing approaches and plugins
+After reading this guide, you will know:
+
+* Rails testing terminology.
+* How to write unit, functional, and integration tests for your application.
+* Other popular testing approaches and plugins.
--------------------------------------------------------------------------------
@@ -75,7 +77,7 @@ steve:
profession: guy with keyboard
```
-Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column.
+Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them.
#### ERB'in It Up
@@ -97,9 +99,9 @@ Rails by default automatically loads all fixtures from the `test/fixtures` folde
* Load the fixture data into the table
* Dump the fixture data into a variable in case you want to access it directly
-#### Fixtures are ActiveRecord objects
+#### Fixtures are Active Record objects
-Fixtures are instances of ActiveRecord. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example:
+Fixtures are instances of Active Record. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example:
```ruby
# this will return the User object for the fixture named david
@@ -828,7 +830,7 @@ Above, the `setup` method is called before each test and so `@post` is available
Let's see the earlier example by specifying `setup` callback by specifying a method name as a symbol:
```ruby
-require '../test_helper'
+require 'test_helper'
class PostsControllerTest < ActionController::TestCase
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 6fb10693ff..b4a59fe3da 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -3,8 +3,6 @@ A Guide for Upgrading Ruby on Rails
This guide provides steps to be followed when you upgrade your applications to a newer version of Ruby on Rails. These steps are also available in individual release guides.
---------------------------------------------------------------------------------
-
General Advice
--------------
@@ -29,7 +27,7 @@ Upgrading from Rails 3.2 to Rails 4.0
NOTE: This section is a work in progress.
-If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting an update to Rails 4.0.
+If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting one to Rails 4.0.
The following changes are meant for upgrading your application to Rails 4.0.
@@ -37,28 +35,21 @@ The following changes are meant for upgrading your application to Rails 4.0.
Rails 4.0 no longer supports loading plugins from `vendor/plugins`. 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, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
-### Identity Map
-
-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: `config.active_record.identity_map`.
-
### Active Record
-The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them.
+* 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: `config.active_record.identity_map`.
-Rails 4.0 has changed how orders get stacked in `ActiveRecord::Relation`. In previous versions of rails new order was applied after previous defined order. But this is no long true. Check [ActiveRecord Query guide](active_record_querying.html#ordering) for more information.
+* The `delete` method in collection associations can now receive `Fixnum` or `String` arguments as record ids, besides records, pretty much like the `destroy` method does. Previously it raised `ActiveRecord::AssociationTypeMismatch` for such arguments. From Rails 4.0 on `delete` automatically tries to find the records matching the given ids before deleting them.
-Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`.
+* Rails 4.0 has changed how orders get stacked in `ActiveRecord::Relation`. In previous versions of Rails, the new order was applied after the previously defined order. But this is no longer true. Check [Active Record Query guide](active_record_querying.html#ordering) for more information.
+
+* Rails 4.0 has changed `serialized_attributes` and `attr_readonly` to class methods only. Now you shouldn't use instance methods, it's deprecated. You must change them, e.g. `self.serialized_attributes` to `self.class.serialized_attributes`.
### Active Model
-Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`.
-Now when confirmation validations fail the error will be attached to
-`:#{attribute}_confirmation` instead of `attribute`.
+* Rails 4.0 has changed how errors attach with the `ActiveModel::Validations::ConfirmationValidator`. Now when confirmation validations fail the error will be attached to `:#{attribute}_confirmation` instead of `attribute`.
-Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default
-value to `false`. Now, Active Model Serializers and Active Record objects have the
-same default behaviour. This means that you can comment or remove the following option
-in the `config/initializers/wrap_parameters.rb` file:
+* Rails 4.0 has changed `ActiveModel::Serializers::JSON.include_root_in_json` default value to `false`. Now, Active Model Serializers and Active Record objects have the same default behaviour. This means that you can comment or remove the following option in the `config/initializers/wrap_parameters.rb` file:
```ruby
# Disable root element in JSON by default.
@@ -69,18 +60,17 @@ in the `config/initializers/wrap_parameters.rb` file:
### Action Pack
-Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature.
+* There is an upgrading cookie store `UpgradeSignatureToEncryptionCookieStore` which helps you upgrading apps that use `CookieStore` to the new default `EncryptedCookieStore`. To use this `CookieStore` set `Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'` in `config/initializers/session_store.rb`. Additionally, add `Myapp::Application.config.secret_key_base = 'some secret'` in `config/initializers/secret_token.rb`. Do not remove `Myapp::Application.config.secret_token = 'some secret'`.
+
+* Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature.
-Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use
-`ActionController::Base.default_static_extension` instead.
+* Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use `ActionController::Base.default_static_extension` instead.
-Rails 4.0 has removed Action and Page caching from ActionPack. You will need to
-add the `actionpack-action_caching` gem in order to use `caches_action` and
-the `actionpack-page_caching` to use `caches_pages` in your controllers.
+* Rails 4.0 has removed Action and Page caching from Action Pack. You will need to add the `actionpack-action_caching` gem in order to use `caches_action` and the `actionpack-page_caching` to use `caches_pages` in your controllers.
-Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`.
+* Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`.
-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, for example:
+* 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, for example:
```ruby
get Rack::Utils.escape('こんにちは'), controller: 'welcome', action: 'index'
@@ -94,11 +84,11 @@ get 'こんにちは', controller: 'welcome', action: 'index'
### Active Support
-Rails 4.0 Removed the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`.
+Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`.
### 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 `helpers_path` 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 `config.railties_order=` method.
+The order in which helpers from more than one directory are loaded has changed in Rails 4.0. Previously, they were gathered and then sorted alphabetically. After upgrading 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 the `helpers_path` parameter, this change will only impact the way of loading helpers from engines. If you rely on the ordering, you should check if correct methods are available after upgrade. If you would like to change the order in which engines are loaded, you can use `config.railties_order=` method.
Upgrading from Rails 3.1 to Rails 3.2
-------------------------------------
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 10b9dddd02..a7ca531123 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -3,13 +3,15 @@ Working with JavaScript in Rails
This guide covers the built-in Ajax/JavaScript functionality of Rails (and
more); it will enable you to create rich and dynamic Ajax applications with
-ease! We will cover the following topics:
+ease!
-* Quick introduction to Ajax
-* Unobtrusive JavaScript
-* How Rails' built-in helpers assist you
-* Handling Ajax on the server side
-* The Turbolinks gem
+After reading this guide, you will know:
+
+* The basics of Ajax.
+* Unobtrusive JavaScript.
+* How Rails' built-in helpers assist you.
+* How to handle Ajax on the server side.
+* The Turbolinks gem.
-------------------------------------------------------------------------------
@@ -64,37 +66,38 @@ Here's the simplest way to write JavaScript. You may see it referred to as
'inline JavaScript':
```html
-<a href="#" onclick="alert('Hello, world.')">Here</a>
+<a href="#" onclick="this.style.backgroundColor='#990000'">Paint it red</a>
```
-
-When clicked, the alert will trigger. Here's the problem: what happens when
-we have lots of JavaScript we want to execute on a click?
+When clicked, the link background will become red. Here's the problem: what
+happens when we have lots of JavaScript we want to execute on a click?
```html
-<a href="#" onclick="function fib(n){return n<2?n:fib(n-1)+fib(n-2);};alert('fib of 15 is: ' + fib(15) + '.');">Calculate</a>
+<a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';">Paint it green</a>
```
Awkward, right? We could pull the function definition out of the click handler,
and turn it into CoffeeScript:
```coffeescript
-fib = (n) ->
- (if n < 2 then n else fib(n - 1) + fib(n - 2))
+paintIt = (element, backgroundColor, textColor) ->
+ element.style.backgroundColor = backgroundColor
+ if textColor?
+ element.style.color = textColor
```
And then on our page:
```html
-<a href="#" onclick="alert('fib of 15 is: ' + fib(15) + '.');">Calculate</a>
+<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a>
```
That's a little bit better, but what about multiple links that have the same
effect?
```html
-<a href="#" onclick="alert('fib of 16 is: ' + fib(16) + '.');">Calculate</a>
-<a href="#" onclick="alert('fib of 17 is: ' + fib(17) + '.');">Calculate</a>
-<a href="#" onclick="alert('fib of 18 is: ' + fib(18) + '.');">Calculate</a>
+<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a>
+<a href="#" onclick="paintIt(this, '#009900', '#FFFFFF')">Paint it green</a>
+<a href="#" onclick="paintIt(this, '#000099', '#FFFFFF')">Paint it blue</a>
```
Not very DRY, eh? We can fix this by using events instead. We'll add a `data-*`
@@ -102,19 +105,21 @@ attribute to our link, and then bind a handler to the click event of every link
that has that attribute:
```coffeescript
-fib = (n) ->
- (if n < 2 then n else fib(n - 1) + fib(n - 2))
-
-$(document).ready ->
- $("a[data-fib]").click (e) ->
- count = $(this).data("fib")
- alert "fib of #{count} is: #{fib(count)}."
-
-... later ...
-
-<a href="#" data-fib="15">Calculate</a>
-<a href="#" data-fib="16">Calculate</a>
-<a href="#" data-fib="17">Calculate</a>
+paintIt = (element, backgroundColor, textColor) ->
+ element.style.backgroundColor = backgroundColor
+ if textColor?
+ element.style.color = textColor
+
+$ ->
+ $("a[data-color]").click ->
+ backgroundColor = $(this).data("background-color")
+ textColor = $(this).data("text-color")
+ paintIt(this, backgroundColor, textColor)
+```
+```html
+<a href="#" data-background-color="#990000">Paint it red</a>
+<a href="#" data-background-color="#009900" data-text-color="#FFFFFF">Paint it green</a>
+<a href="#" data-background-color="#000099" data-text-color="#FFFFFF">Paint it blue</a>
```
We call this 'unobtrusive' JavaScript because we're no longer mixing our
@@ -202,7 +207,7 @@ is a helper that assists with generating links. It has a `:remote` option you
can use like this:
```erb
-<%= link_to "first post", @post, remote: true %>
+<%= link_to "a post", @post, remote: true %>
```
which generates
@@ -212,20 +217,19 @@ which generates
```
You can bind to the same Ajax events as `form_for`. Here's an example. Let's
-assume that we have a resource `/fib/:n` that calculates the `n`th Fibonacci
-number. We would generate some HTML like this:
+assume that we have a list of posts that can be deleted with just one
+click. We would generate some HTML like this:
```erb
-<%= link_to "Calculate", "/fib/15", remote: true, data: { fib: 15 } %>
+<%= link_to "Delete post", @post, remote: true, method: :delete %>
```
and write some CoffeeScript like this:
```coffeescript
-$(document).ready ->
- $("a[data-fib]").on "ajax:success", (e, data, status, xhr) ->
- count = $(this).data("fib")
- alert "fib of #{count} is: #{data}."
+$ ->
+ $("a[data-remote]").on "ajax:success", (e, data, status, xhr) ->
+ alert "The post was deleted."
```
### button_to
diff --git a/install.rb b/install.rb
index b87b008c2e..3967624a3f 100644
--- a/install.rb
+++ b/install.rb
@@ -1,11 +1,16 @@
version = ARGV.pop
+if version.nil?
+ puts "Usage: ruby install.rb version"
+ exit(64)
+end
+
%w( activesupport activemodel activerecord actionpack actionmailer railties ).each do |framework|
puts "Installing #{framework}..."
- `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem`
+ `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --local --no-ri --no-rdoc && rm #{framework}-#{version}.gem`
end
puts "Installing Rails..."
`gem build rails.gemspec`
-`gem install rails-#{version}.gem --no-ri --no-rdoc `
+`gem install rails-#{version}.gem --local --no-ri --no-rdoc `
`rm rails-#{version}.gem`
diff --git a/rails.gemspec b/rails.gemspec
index ba94354bf1..128b312424 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -26,6 +26,6 @@ Gem::Specification.new do |s|
s.add_dependency 'actionmailer', version
s.add_dependency 'railties', version
- s.add_dependency 'bundler', '>= 1.2.2', '< 2.0'
+ s.add_dependency 'bundler', '>= 1.3.0.pre.4', '< 2.0'
s.add_dependency 'sprockets-rails', '~> 2.0.0.rc1'
end
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index c797eacd0b..e8a91af7af 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,20 +1,71 @@
## Rails 4.0.0 (unreleased) ##
-* Add sqlserver.yml template file to satisfy '-d sqlserver' being passed to 'rails new'.
- Fix #6882
-
- *Robert Nesius*
+* `config.assets.enabled` is now true by default. If you're upgrading from a Rails 3.x app
+ that does not use the asset pipeline, you'll be required to add `config.assets.enabled = false`
+ to your application.rb. If you don't want the asset pipeline on a new app use `--skip-sprockets`
-* Rake test:uncommitted finds git directory in ancestors *Nicolas Despres*
+ *DHH*
-* Add dummy app Rake tasks when --skip-test-unit and --dummy-path is passed to the plugin generator.
- Fix #8121
+* Environment name can be a start substring of the default environment names
+ (production, development, test). For example: tes, pro, prod, dev, devel.
+ Fix #8628.
+
+ *Mykola Kyryk*
+
+* Add `-B` alias for `--skip-bundle` option in the rails new generators.
+
+ *Jiri Pospisil*
+
+* Quote column names in generates fixture files. This prevents
+ conflicts with reserved YAML keywords such as 'yes' and 'no'
+ Fix #8612.
*Yves Senn*
-* Ensure that RAILS_ENV is set when accessing Rails.env *Steve Klabnik*
+* Explicit options have precedence over `~/.railsrc` on the `rails new` command.
+
+ *Rafael Mendonça França*
+
+* Generated migrations now always use the `change` method.
+
+ *Marc-André Lafortune*
+
+* Add `app/models/concerns` and `app/controllers/concerns` to the default directory structure and load path.
+ See http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns for usage instructions.
+
+ *DHH*
+
+* The `rails/info/routes` now correctly formats routing output as an html table.
+
+ *Richard Schneeman*
+
+* The `public/index.html` is no longer generated for new projects.
+ Page is replaced by internal `welcome_controller` inside of railties.
+
+ *Richard Schneeman*
+
+* Add `ENV['RACK_ENV']` support to `rails runner/console/server`.
-* Don't eager-load app/assets and app/views *Elia Schito*
+ *kennyj*
+
+* Add `db` to list of folders included by `rake notes` and `rake notes:custom`. *Antonio Cangiano*
+
+* Engines with a dummy app include the rake tasks of dependencies in the app namespace.
+ Fix #8229
+
+ *Yves Senn*
+
+* Add `sqlserver.yml` template file to satisfy `-d sqlserver` being passed to `rails new`.
+ Fix #6882
+
+ *Robert Nesius*
+
+* Rake test:uncommitted finds git directory in ancestors *Nicolas Despres*
+
+* Add dummy app Rake tasks when `--skip-test-unit` and `--dummy-path` is passed to the plugin generator.
+ Fix #8121
+
+ *Yves Senn*
* Add `.rake` to list of file extensions included by `rake notes` and `rake notes:custom`. *Brent J. Nordquist*
@@ -97,10 +148,6 @@
* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
-* Add `config.queue_consumer` to change the job queue consumer from the default `ActiveSupport::ThreadedQueueConsumer`. *Carlos Antonio da Silva*
-
-* Add `Rails.queue` for processing jobs in the background. *Yehuda Katz*
-
* Remove Rack::SSL in favour of ActionDispatch::SSL. *Rafael Mendonça França*
* Remove Active Resource from Rails framework. *Prem Sichangrist*
@@ -120,8 +167,6 @@
* Add convenience `hide!` method to Rails generators to hide current generator
namespace from showing when running `rails generate`. *Carlos Antonio da Silva*
-* Scaffold now uses `content_tag_for` in index.html.erb *José Valim*
-
* Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
* Set config.action_mailer.async = true to turn on asynchronous
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index 03bde18130..0d7fb865e2 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2012 David Heinemeier Hansson
+Copyright (c) 2004-2013 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 6bf2d8db20..bb98bbe5bf 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -21,41 +21,17 @@ end
module Rails
autoload :Info, 'rails/info'
- autoload :InfoController, 'rails/info_controller'
+ autoload :InfoController, 'rails/info_controller'
+ autoload :WelcomeController, 'rails/welcome_controller'
class << self
- def application
- @application ||= nil
- end
-
- def application=(application)
- @application = application
- end
+ attr_accessor :application, :cache, :logger
# The Configuration instance used to configure the Rails environment
def configuration
application.config
end
- # Rails.queue is the application's queue. You can push a job onto
- # the queue by:
- #
- # Rails.queue.push job
- #
- # A job is an object that responds to +run+. Queue consumers will
- # pop jobs off of the queue and invoke the queue's +run+ method.
- #
- # Note that depending on your queue implementation, jobs may not
- # be executed in the same process as they were created in, and
- # are never executed in the same thread as they were created in.
- #
- # If necessary, a queue implementation may need to serialize your
- # job for distribution to another process. The documentation of
- # your queue will specify the requirements for that serialization.
- def queue
- application.queue
- end
-
def initialize!
application.initialize!
end
@@ -64,14 +40,6 @@ module Rails
application.initialized?
end
- def logger
- @logger ||= nil
- end
-
- def logger=(logger)
- @logger = logger
- end
-
def backtrace_cleaner
@backtrace_cleaner ||= begin
# Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded
@@ -85,24 +53,13 @@ module Rails
end
def env
- @_env ||= begin
- ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
- ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"])
- end
+ @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development")
end
def env=(environment)
@_env = ActiveSupport::StringInquirer.new(environment)
end
- def cache
- @cache ||= nil
- end
-
- def cache=(cache)
- @cache = cache
- end
-
# Returns all rails groups for loading based on:
#
# * The Rails environment;
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index ae3993fbd8..cff75872b2 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,5 +1,4 @@
require 'fileutils'
-require 'active_support/queueing'
# FIXME remove DummyKeyGenerator and this require in 4.1
require 'active_support/key_generator'
require 'rails/engine'
@@ -68,10 +67,9 @@ module Rails
end
end
- attr_accessor :assets, :sandbox, :queue_consumer
+ attr_accessor :assets, :sandbox
alias_method :sandbox?, :sandbox
attr_reader :reloaders
- attr_writer :queue
delegate :default_url_options, :default_url_options=, to: :routes
@@ -83,7 +81,6 @@ module Rails
@env_config = nil
@ordered_railties = nil
@railties = nil
- @queue = nil
end
# Returns true if the application is initialized.
@@ -123,6 +120,7 @@ module Rails
# Currently stores:
#
# * "action_dispatch.parameter_filter" => config.filter_parameters
+ # * "action_dispatch.redirect_filter" => config.filter_redirect
# * "action_dispatch.secret_token" => config.secret_token,
# * "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
# * "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local
@@ -134,14 +132,13 @@ module Rails
# * "action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt
# * "action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt
#
- # These parameters will be used by middlewares and engines to configure themselves
- #
def env_config
@env_config ||= begin
if config.secret_key_base.nil?
- ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base. " +
- "This should be used instead of the old deprecated config.secret_token. " +
- "Set config.secret_key_base instead of config.secret_token in config/initializers/secret_token.rb"
+ ActiveSupport::Deprecation.warn "You didn't set config.secret_key_base in config/initializers/secret_token.rb file. " +
+ "This should be used instead of the old deprecated config.secret_token in order to use the new EncryptedCookieStore. " +
+ "To convert safely to the encrypted store (without losing existing cookies and sessions), see http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#action-pack"
+
if config.secret_token.blank?
raise "You must set config.secret_key_base in your app's config"
end
@@ -149,6 +146,7 @@ module Rails
super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
+ "action_dispatch.redirect_filter" => config.filter_redirect,
"action_dispatch.secret_token" => config.secret_token,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
@@ -226,10 +224,6 @@ module Rails
@config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd))
end
- def queue #:nodoc:
- @queue ||= config.queue || ActiveSupport::Queue.new
- end
-
def to_app #:nodoc:
self
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index f97e66985c..2c7ddd86e7 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/kernel/reporting'
require 'active_support/file_update_checker'
-require 'active_support/queueing'
require 'rails/engine/configuration'
module Rails
@@ -13,7 +12,7 @@ module Rails
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
:serve_static_assets, :ssl_options, :static_cache_control, :session_options,
:time_zone, :reload_classes_only_on_change,
- :queue, :queue_consumer, :beginning_of_week
+ :beginning_of_week, :filter_redirect
attr_writer :log_level
attr_reader :encoding
@@ -23,6 +22,7 @@ module Rails
self.encoding = "utf-8"
@consider_all_requests_local = false
@filter_parameters = []
+ @filter_redirect = []
@helpers_paths = []
@serve_static_assets = true
@static_cache_control = nil
@@ -43,19 +43,17 @@ module Rails
@exceptions_app = nil
@autoflush_log = true
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new
- @queue = ActiveSupport::SynchronousQueue.new
- @queue_consumer = nil
@eager_load = nil
@secret_token = nil
@secret_key_base = nil
@assets = ActiveSupport::OrderedOptions.new
- @assets.enabled = false
+ @assets.enabled = true
@assets.paths = []
@assets.precompile = [ Proc.new { |path, fn| fn =~ /app\/assets/ && !%w(.js .css).include?(File.extname(path)) },
/(?:\/|\\|\A)application\.(css|js)$/ ]
@assets.prefix = "/assets"
- @assets.version = ''
+ @assets.version = '1.0'
@assets.debug = false
@assets.compile = true
@assets.digest = false
@@ -105,6 +103,10 @@ module Rails
def database_configuration
require 'erb'
YAML.load ERB.new(IO.read(paths["config/database"].first)).result
+ rescue Psych::SyntaxError => e
+ raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
+ "Error: #{e.message}"
end
def log_level
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 2d87b8594a..872d78d9a4 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -25,6 +25,7 @@ module Rails
get '/rails/info/properties' => "rails/info#properties"
get '/rails/info/routes' => "rails/info#routes"
get '/rails/info' => "rails/info#index"
+ get '/' => "rails/welcome#index"
end
end
end
@@ -95,15 +96,6 @@ module Rails
ActiveSupport::Dependencies.unhook!
end
end
-
- initializer :activate_queue_consumer do |app|
- if config.queue.class == ActiveSupport::Queue
- app.queue_consumer = config.queue_consumer || config.queue.consumer
- app.queue_consumer.logger ||= Rails.logger if app.queue_consumer.respond_to?(:logger=)
- app.queue_consumer.start
- at_exit { app.queue_consumer.shutdown }
- end
- end
end
end
end
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 1aed2796c1..039360fcf6 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -1,6 +1,12 @@
class CodeStatistics #:nodoc:
- TEST_TYPES = %w(Units Functionals Unit\ tests Functional\ tests Integration\ tests)
+ TEST_TYPES = ['Controller tests',
+ 'Helper tests',
+ 'Model tests',
+ 'Mailer tests',
+ 'Integration tests',
+ 'Functional tests (old)',
+ 'Unit tests (old)']
def initialize(*pairs)
@pairs = pairs
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index b0fae13192..5ccec8082c 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -60,7 +60,7 @@ when 'console'
require 'rails/commands/console'
options = Rails::Console.parse_arguments(ARGV)
- # RAILS_ENV needs to be set before config/application is required
+ # 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
@@ -77,13 +77,13 @@ when 'server'
Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))
require 'rails/commands/server'
- Rails::Server.new.tap { |server|
+ Rails::Server.new.tap do |server|
# We need to require application after the server sets environment,
# otherwise the --environment option given to the server won't propagate.
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
- }
+ end
when 'dbconsole'
require 'rails/commands/dbconsole'
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index ff0eda3413..2d9708e5b5 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -14,8 +14,7 @@ else
extra_args_string = File.open(railsrc).read
extra_args = extra_args_string.split(/\n+/).map {|l| l.split}.flatten
puts "Using #{extra_args.join(" ")} from #{railsrc}"
- ARGV << extra_args
- ARGV.flatten!
+ ARGV.insert(1, *extra_args)
end
end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 92cee6b638..86ab1aabbf 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -24,11 +24,21 @@ module Rails
if arguments.first && arguments.first[0] != '-'
env = arguments.first
- options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ if available_environments.include? env
+ options[:environment] = env
+ else
+ options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ end
end
options
end
+
+ private
+
+ def available_environments
+ Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') }
+ end
end
attr_reader :options, :app, :console
@@ -45,7 +55,7 @@ module Rails
end
def environment
- options[:environment] ||= ENV['RAILS_ENV'] || 'development'
+ options[:environment] ||= ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
end
def environment?
@@ -79,13 +89,11 @@ module Rails
end
def require_debugger
- begin
- require 'debugger'
- puts "=> Debugger enabled"
- rescue Exception
- puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
- exit
- end
+ require 'debugger'
+ puts "=> Debugger enabled"
+ rescue LoadError
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
+ exit
end
end
end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index 90359d1c08..5914c9e4ae 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -5,8 +5,8 @@ require 'rbconfig'
module Rails
class DBConsole
- attr_reader :config, :arguments
-
+ attr_reader :arguments
+
def self.start
new.start
end
@@ -59,7 +59,7 @@ module Rails
args << "-#{options['mode']}" if options['mode']
args << "-header" if options['header']
- args << File.expand_path(config['database'], Rails.root)
+ args << File.expand_path(config['database'], Rails.respond_to?(:root) ? Rails.root : nil)
find_cmd_and_exec('sqlite3', *args)
@@ -82,17 +82,13 @@ module Rails
def config
@config ||= begin
cfg = begin
- cfg = YAML.load(ERB.new(IO.read("config/database.yml")).result)
+ 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]
+ cfg[environment] || abort("No database is configured for the environment '#{environment}'")
end
end
@@ -108,7 +104,7 @@ module Rails
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|
@@ -123,7 +119,7 @@ module Rails
opt.on("--header") do |h|
options['header'] = h
end
-
+
opt.on("-h", "--help", "Show this help message.") do
puts opt
exit
@@ -140,12 +136,20 @@ module Rails
if arguments.first && arguments.first[0] != '-'
env = arguments.first
- options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ if available_environments.include? env
+ options[:environment] = env
+ else
+ options[:environment] = %w(production development test).detect {|e| e =~ /^#{env}/} || env
+ end
end
-
+
options
end
+ def available_environments
+ Dir['config/environments/*.rb'].map { |fname| File.basename(fname, '.*') }
+ end
+
def find_cmd_and_exec(commands, *args)
commands = Array(commands)
diff --git a/railties/lib/rails/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb
index 3f6966b4f0..315bcccf61 100644
--- a/railties/lib/rails/commands/profiler.rb
+++ b/railties/lib/rails/commands/profiler.rb
@@ -8,7 +8,7 @@ def options
defaults = ActiveSupport::Testing::Performance::DEFAULTS
OptionParser.new do |opt|
- opt.banner = "Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS]"
+ opt.banner = "Usage: rails profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS]"
opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r }
opt.on('-o', '--output PATH', String, 'Directory to use when writing the results.', "Default: #{defaults[:output]}") { |o| options[:output] = o }
opt.on('-m', '--metrics a,b,c', Array, 'Metrics to use.', "Default: #{defaults[:metrics].join(",")}") { |m| options[:metrics] = m.map(&:to_sym) }
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 0cc672e01c..6adbdc6e0b 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -1,7 +1,7 @@
require 'optparse'
require 'rbconfig'
-options = { environment: (ENV['RAILS_ENV'] || "development").dup }
+options = { environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup }
code_or_file = nil
if ARGV.first.nil?
@@ -41,7 +41,7 @@ ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
- Rails.application.load_runner
+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 0b897d736d..cdb29a8156 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -108,7 +108,7 @@ module Rails
super.merge({
Port: 3000,
DoNotReverseLookup: true,
- environment: (ENV['RAILS_ENV'] || "development").dup,
+ environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
debugger: false,
pid: File.expand_path("tmp/pids/server.pid"),
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 2c2bb1c714..3ba62039de 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -106,7 +106,7 @@ module Rails
#
# The <tt>Application</tt> class adds a couple more paths to this set. And as in your
# <tt>Application</tt>, all folders under +app+ are automatically added to the load path.
- # If you have an <tt>app/observers</tt> folder for example, it will be added by default.
+ # If you have an <tt>app/services/tt> folder for example, it will be added by default.
#
# == Endpoint
#
@@ -407,8 +407,10 @@ module Rails
end
end
+ self.isolated = false
+
delegate :middleware, :root, :paths, to: :config
- delegate :engine_name, :isolated?, to: "self.class"
+ delegate :engine_name, :isolated?, to: :class
def initialize
@_all_autoload_paths = nil
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 22e885a3a6..10d1821709 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -38,6 +38,7 @@ module Rails
def paths
@paths ||= begin
paths = Rails::Paths::Root.new(@root)
+
paths.add "app", eager_load: true, glob: "*"
paths.add "app/assets", glob: "*"
paths.add "app/controllers", eager_load: true
@@ -45,19 +46,27 @@ module Rails
paths.add "app/models", eager_load: true
paths.add "app/mailers", eager_load: true
paths.add "app/views"
+
+ paths.add "app/controllers/concerns", eager_load: true
+ paths.add "app/models/concerns", eager_load: true
+
paths.add "lib", load_path: true
paths.add "lib/assets", glob: "*"
paths.add "lib/tasks", glob: "**/*.rake"
+
paths.add "config"
paths.add "config/environments", glob: "#{Rails.env}.rb"
paths.add "config/initializers", glob: "**/*.rb"
paths.add "config/locales", glob: "*.{rb,yml}"
paths.add "config/routes.rb"
+
paths.add "db"
paths.add "db/migrate"
paths.add "db/seeds.rb"
+
paths.add "vendor", load_path: true
paths.add "vendor/assets", glob: "*"
+
paths
end
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 367f9288b8..d9a91b74d1 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -172,13 +172,11 @@ module Rails
"resource_route",
"#{orm}:migration",
"#{orm}:model",
- "#{orm}:observer",
"#{test}:controller",
"#{test}:helper",
"#{test}:integration",
"#{test}:mailer",
"#{test}:model",
- "#{test}:observer",
"#{test}:scaffold",
"#{test}:view",
"#{test}:performance",
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 5c4e81431c..b96ee9295e 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -78,7 +78,7 @@ module Rails
# end
#
# environment(nil, env: "development") do
- # "config.active_record.observers = :cacher"
+ # "config.autoload_paths += %W(#{config.root}/extras)"
# end
def environment(data=nil, options={}, &block)
sentinel = /class [a-z_:]+ < Rails::Application/i
diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb
index 0e51b9c568..6183944bb0 100644
--- a/railties/lib/rails/generators/active_model.rb
+++ b/railties/lib/rails/generators/active_model.rb
@@ -59,8 +59,8 @@ module Rails
end
# PATCH/PUT update
- def update_attributes(params=nil)
- "#{name}.update_attributes(#{params})"
+ def update(params=nil)
+ "#{name}.update(#{params})"
end
# POST create
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index de3127f43e..ca3652c703 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -28,7 +28,7 @@ module Rails
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
- class_option :skip_bundle, type: :boolean, default: false,
+ class_option :skip_bundle, type: :boolean, aliases: '-B', default: false,
desc: "Don't run bundle install"
class_option :skip_git, type: :boolean, aliases: '-G', default: false,
@@ -52,9 +52,6 @@ module Rails
class_option :skip_javascript, type: :boolean, aliases: '-J', default: false,
desc: 'Skip JavaScript files'
- class_option :skip_index_html, type: :boolean, aliases: '-I', default: false,
- desc: 'Skip public/index.html and app/assets/images/rails.png files'
-
class_option :dev, type: :boolean, default: false,
desc: "Setup the #{name} with Gemfile pointing to your Rails checkout"
@@ -141,14 +138,12 @@ module Rails
if options.dev?
<<-GEMFILE.strip_heredoc
gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}'
- gem 'journey', github: 'rails/journey'
gem 'arel', github: 'rails/arel'
gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
gem 'rails', github: 'rails/rails'
- gem 'journey', github: 'rails/journey'
gem 'arel', github: 'rails/arel'
gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders'
GEMFILE
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index f5182bcc50..90d8db1df5 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -3,9 +3,9 @@
<table>
<thead>
<tr>
- <% attributes.each do |attribute| -%>
+<% attributes.each do |attribute| -%>
<th><%= attribute.human_name %></th>
- <% end -%>
+<% end -%>
<th></th>
<th></th>
<th></th>
@@ -13,13 +13,15 @@
</thead>
<tbody>
- <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %>
- <% attributes.each do |attribute| -%>
+ <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
+ <tr>
+<% attributes.each do |attribute| -%>
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
- <% end -%>
+<% end -%>
<td><%%= link_to 'Show', <%= singular_table_name %> %></td>
<td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
<td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ </tr>
<%% end %>
</tbody>
</table>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index d8a4f15b4b..4ae8756ed0 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -99,13 +99,17 @@ module Rails
end
def index_name
- @index_name ||= if reference?
- polymorphic? ? %w(id type).map { |t| "#{name}_#{t}" } : "#{name}_id"
+ @index_name ||= if polymorphic?
+ %w(id type).map { |t| "#{name}_#{t}" }
else
- name
+ column_name
end
end
+ def column_name
+ @column_name ||= reference? ? "#{name}_id" : name
+ end
+
def foreign_key?
!!(name =~ /_id$/)
end
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 84f8f76838..9965db98de 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -160,6 +160,13 @@ module Rails
end
end
+ def attributes_names
+ @attributes_names ||= attributes.each_with_object([]) do |a, names|
+ names << a.column_name
+ names << "#{a.name}_type" if a.polymorphic?
+ end
+ end
+
def pluralize_table_names?
!defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names
end
@@ -169,10 +176,10 @@ module Rails
#
# ==== Examples
#
- # check_class_collision suffix: "Observer"
+ # check_class_collision suffix: "Decorator"
#
# If the generator is invoked with class name Admin, it will check for
- # the presence of "AdminObserver".
+ # the presence of "AdminDecorator".
#
def self.check_class_collision(options={})
define_method :check_class_collision do
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 18637451ac..372790df59 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -55,8 +55,12 @@ module Rails
def app
directory 'app'
+
keep_file 'app/mailers'
keep_file 'app/models'
+
+ keep_file 'app/controllers/concerns'
+ keep_file 'app/models/concerns'
end
def config
@@ -97,11 +101,6 @@ module Rails
def public_directory
directory "public", "public", recursive: false
- if options[:skip_index_html]
- remove_file "public/index.html"
- remove_file 'app/assets/images/rails.png'
- keep_file 'app/assets/images'
- end
end
def script
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 5b7a653a09..c4846b2c11 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -21,5 +21,7 @@ source 'https://rubygems.org'
# Deploy with Capistrano
# gem 'capistrano', group: :development
+<% unless defined?(JRUBY_VERSION) -%>
# To use debugger
# gem 'debugger'
+<% end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index b5d7b6436b..2bd7c27f2a 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -57,7 +57,7 @@ shown in the browser on requests from 127.0.0.1.
You can also log your own messages directly into the log file from your code
using the Ruby logger class from inside your controllers. Example:
- class WeblogController < ActionController::Base
+ class WeblogsController < ActionController::Base
def destroy
@weblog = Weblog.find(params[:id])
@weblog.destroy
@@ -89,7 +89,7 @@ execution at any point in the code, investigate and change the model, and then,
resume execution! You need to install the 'debugger' gem to run the server in debugging
mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example:
- class WeblogController < ActionController::Base
+ class PostsController < ActionController::Base
def index
@posts = Post.all
debugger
@@ -100,17 +100,15 @@ So the controller will accept the action, run the first line, then present you
with a IRB prompt in the server window. Here you can do things like:
>> @posts.inspect
- => "[#<Post:0x14a6be8
- @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
- #<Post:0x14a6620
- @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
+ => "#<ActiveRecord::Relation [#<Post id: 1, title: nil, body: nil>,
+ #<Post id: 2, title: \"Rails\", body: "Only ten..">]>"
>> @posts.first.title = "hello from a debugger"
=> "hello from a debugger"
...and even better, you can examine how your runtime objects actually work:
>> f = @posts.first
- => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
+ => #<Post id: 1, title: nil, body: nil>
>> f.
Display all 152 possibilities? (y or n)
@@ -157,13 +155,15 @@ The default directory structure of a generated Ruby on Rails application:
|-- app
| |-- assets
- | |-- images
- | |-- javascripts
- | `-- stylesheets
+ | | |-- images
+ | | |-- javascripts
+ | | `-- stylesheets
| |-- controllers
+ | | `-- concerns
| |-- helpers
| |-- mailers
| |-- models
+ | | `-- concerns
| `-- views
| `-- layouts
|-- config
@@ -173,23 +173,25 @@ The default directory structure of a generated Ruby on Rails application:
|-- db
|-- doc
|-- lib
+ | |-- assets
| `-- tasks
|-- log
|-- public
|-- script
|-- test
+ | |-- controllers
| |-- fixtures
- | |-- functional
+ | |-- helpers
| |-- integration
- | |-- performance
- | `-- unit
+ | |-- mailers
+ | |-- models
+ | `-- performance
|-- tmp
- | |-- cache
- | |-- pids
- | |-- sessions
- | `-- sockets
+ | `-- cache
+ | `-- assets
`-- vendor
- |-- assets
+ `-- assets
+ |-- javascripts
`-- stylesheets
app
@@ -216,7 +218,7 @@ app/views/layouts
Holds the template files for layouts to be used with views. This models the
common header/footer method of wrapping views. In your views, define a layout
using the <tt>layout :default</tt> and create a file named default.html.erb.
- Inside default.html.erb, call <% yield %> to render the view using this
+ Inside default.html.erb, call <%= yield %> to render the view using this
layout.
app/helpers
@@ -255,5 +257,5 @@ test
directory.
vendor
- External libraries that the application depends on. If the app has frozen rails,
- those gems also go here, under vendor/rails/. This directory is in the load path.
+ External libraries that the application depends on. This directory is in the
+ load path.
diff --git a/railties/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory b/railties/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/mailers/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index e0539aa8bb..d87c7b7268 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -2,8 +2,8 @@
<html>
<head>
<title><%= camelized %></title>
- <%%= stylesheet_link_tag "application", media: "all" %>
- <%%= javascript_include_tag "application" %>
+ <%%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
+ <%%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%%= csrf_meta_tags %>
</head>
<body>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 5f15c973c6..f5d7d698a3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -22,21 +22,10 @@ module <%= app_const_base %>
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
+<% if options.skip_sprockets? -%>
- # Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters += [:password]
-
- # Use SQL instead of Active Record's schema dumper when creating the database.
- # This is necessary if your schema can't be completely dumped by the schema dumper,
- # like if you have constraints or database-specific column types.
- # config.active_record.schema_format = :sql
-
-<% unless options.skip_sprockets? -%>
- # Enable the asset pipeline.
- config.assets.enabled = true
-
- # Version of your assets, change this if you want to expire all your assets.
- config.assets.version = '1.0'
+ # Disable the asset pipeline.
+ config.assets.enabled = false
<% end -%>
end
end
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 22c9194fad..eb569b7dab 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
@@ -26,7 +26,7 @@ development:
# domain socket that doesn't need configuration. Windows does not have
# domain sockets, so uncomment these lines.
#host: localhost
-
+
# The TCP port the server listens on. Defaults to 5432.
# If your server runs on a different port number, change accordingly.
#port: 5432
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
index b52b733c56..53620dc8e2 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
@@ -1,5 +1,5 @@
# SQL Server (2005 or higher recommended)
-#
+#
# Install the adapters and driver
# gem install tiny_tds
# gem install activerecord-sqlserver-adapter
@@ -8,8 +8,8 @@
# gem 'tiny_tds'
# gem 'activerecord-sqlserver-adapter'
#
-# You should make sure freetds is configured correctly first.
-# freetds.conf contains host/port/protocol_versions settings.
+# You should make sure freetds is configured correctly first.
+# freetds.conf contains host/port/protocol_versions settings.
# http://freetds.schemamania.org/userguide/freetdsconf.htm
#
# A typical Microsoft server
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 593d2acfc7..0ab91d9864 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -32,6 +32,9 @@
# Generate digests for assets URLs.
config.assets.digest = true
+
+ # Version of your assets, change this if you want to expire all your assets.
+ config.assets.version = '1.0'
<%- end -%>
# Specifies the header that your server uses for sending files.
@@ -84,8 +87,4 @@
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
-
- # Default the production mode queue to an synchronous queue. You will probably
- # want to replace this with an out-of-process queueing solution.
- # config.queue = ActiveSupport::SynchronousQueue.new
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index a5ef0cd9cd..3c9c787948 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -33,7 +33,4 @@
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
-
- # Use the synchronous queue to run jobs immediately.
- config.queue = ActiveSupport::SynchronousQueue.new
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb
new file mode 100644
index 0000000000..4a994e1e7b
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Configure sensitive parameters which will be filtered from the log file.
+Rails.application.config.filter_parameters += [:password]
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb
index a8285f88ca..d89dac7c6a 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb
@@ -1,3 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# Rails.application.config.time_zone = 'Central Time (US & Canada)'
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 631543c705..22a6aeb5fe 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -2,7 +2,7 @@
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
- # You can have the root of your site routed with "root" just remember to delete public/index.html.
+ # You can have the root of your site routed with "root"
# root to: 'welcome#index'
# Example of regular route:
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 8910bf5a06..25a742dff0 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -2,9 +2,9 @@
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
-# git config --global core.excludesfile ~/.gitignore_global
+# git config --global core.excludesfile '~/.gitignore_global'
-# Ignore bundler config
+# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
diff --git a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory b/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory
+++ /dev/null
diff --git a/railties/lib/rails/generators/rails/controller/USAGE b/railties/lib/rails/generators/rails/controller/USAGE
index 9def4af65c..64239ad599 100644
--- a/railties/lib/rails/generators/rails/controller/USAGE
+++ b/railties/lib/rails/generators/rails/controller/USAGE
@@ -6,7 +6,7 @@ Description:
path like 'parent_module/controller_name'.
This generates a controller class in app/controllers and invokes helper,
- template engine and test framework generators.
+ template engine, assets, and test framework generators.
Example:
`rails generate controller CreditCards open debit credit close`
diff --git a/railties/lib/rails/generators/rails/migration/USAGE b/railties/lib/rails/generators/rails/migration/USAGE
index af74963b01..baf3d9894f 100644
--- a/railties/lib/rails/generators/rails/migration/USAGE
+++ b/railties/lib/rails/generators/rails/migration/USAGE
@@ -15,15 +15,21 @@ Example:
`rails generate migration AddTitleBodyToPost title:string body:text published:boolean`
- This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with
- this in the Up migration:
+ This will create the AddTitleBodyToPost in db/migrate/20080514090912_add_title_body_to_post.rb with this in the Change migration:
add_column :posts, :title, :string
add_column :posts, :body, :text
add_column :posts, :published, :boolean
- And this in the Down migration:
+Migration names containing JoinTable will generate join tables for use with
+has_and_belongs_to_many associations.
- remove_column :posts, :published
- remove_column :posts, :body
- remove_column :posts, :title
+Example:
+ `rails g migration CreateMediaJoinTable artists musics:uniq`
+
+ will create the migration
+
+ create_join_table :artists, :musics do |t|
+ # t.index [:artist_id, :music_id]
+ t.index [:music_id, :artist_id], unique: true
+ end
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index e29e19490e..6574200fbf 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -21,12 +21,12 @@ Description:
Available field types:
- Just after the field name you can specify a type like text or boolean.
+ Just after the field name you can specify a type like text or boolean.
It will generate the column with the associated SQL type. For instance:
`rails generate model post title:string body:text`
- will generate a title column with a varchar type and a body column with a text
+ will generate a title column with a varchar type and a body column with a text
type. You can use the following types:
integer
@@ -57,16 +57,16 @@ Available field types:
limit Set the maximum size of the field giving a number between curly braces
default Set a default value for the field
- precision Defines the precision for the decimal fields
+ precision Defines the precision for the decimal fields
scale Defines the scale for the decimal fields
- uniq Defines the field values as unique
+ uniq Defines the field values as unique
index Will add an index on the field
Examples:
`rails generate model user pseudo:string{30}`
`rails generate model user pseudo:string:uniq`
-
+
Examples:
`rails generate model account`
@@ -76,7 +76,7 @@ Examples:
Model: app/models/account.rb
Test: test/models/account_test.rb
Fixtures: test/fixtures/accounts.yml
- Migration: db/migrate/XXX_add_accounts.rb
+ Migration: db/migrate/XXX_create_accounts.rb
`rails generate model post title:string body:text published:boolean`
@@ -90,5 +90,5 @@ Examples:
Model: app/models/admin/account.rb
Test: test/models/admin/account_test.rb
Fixtures: test/fixtures/admin/accounts.yml
- Migration: db/migrate/XXX_add_admin_accounts.rb
+ Migration: db/migrate/XXX_create_admin_accounts.rb
diff --git a/railties/lib/rails/generators/rails/observer/USAGE b/railties/lib/rails/generators/rails/observer/USAGE
deleted file mode 100644
index 177ff49e4a..0000000000
--- a/railties/lib/rails/generators/rails/observer/USAGE
+++ /dev/null
@@ -1,12 +0,0 @@
-Description:
- Stubs out a new observer. Pass the observer name, either CamelCased or
- under_scored, as an argument.
-
- This generator only invokes your ORM and test framework generators.
-
-Example:
- `rails generate observer Account`
-
- For ActiveRecord and TestUnit it creates:
- Observer: app/models/account_observer.rb
- TestUnit: test/models/account_observer_test.rb
diff --git a/railties/lib/rails/generators/rails/observer/observer_generator.rb b/railties/lib/rails/generators/rails/observer/observer_generator.rb
deleted file mode 100644
index 7a4d701ac6..0000000000
--- a/railties/lib/rails/generators/rails/observer/observer_generator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Rails
- module Generators
- class ObserverGenerator < NamedBase # :nodoc:
- hook_for :orm, required: true
- end
- end
-end
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 6f25cd266e..cd756a729d 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
@@ -173,7 +173,7 @@ task default: :test
"skip adding entry to Gemfile"
def initialize(*args)
- raise Error, "Options should be given after the plugin name. For details run: rails plugin --help" if args[0].blank?
+ raise Error, "Options should be given after the plugin name. For details run: rails plugin new --help" if args[0].blank?
@dummy_path = nil
super
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index 6701630a77..a8b5bfaf3f 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -1,4 +1,4 @@
-source "http://rubygems.org"
+source "https://rubygems.org"
<% if options[:skip_gemspec] -%>
<%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>"
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
index 8a8ba04a70..310c975262 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
@@ -11,7 +11,7 @@ require "action_mailer/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
-Bundler.require
+Bundler.require(*Rails.groups)
require "<%= name %>"
<%= application_definition %>
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index b4f466fbd8..dd636ed3cf 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -2,13 +2,22 @@ require 'rails/generators/rails/resource/resource_generator'
module Rails
module Generators
- class ScaffoldGenerator < ResourceGenerator # :nodoc:
+ class ScaffoldGenerator < ResourceGenerator # :nodoc:
remove_hook_for :resource_controller
remove_class_option :actions
class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets"
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
+ class_option :html, type: :boolean, default: true,
+ desc: "Generate a scaffold with HTML output"
+
+ def handle_skip
+ if !options[:html] || !options[:stylesheets]
+ @options = @options.merge(stylesheet_engine: false)
+ end
+ end
+
hook_for :scaffold_controller, required: true
hook_for :assets do |assets|
@@ -16,7 +25,9 @@ module Rails
end
hook_for :stylesheet_engine do |stylesheet_engine|
- invoke stylesheet_engine, [controller_name] if options[:stylesheets] && behavior == :invoke
+ if behavior == :invoke
+ invoke stylesheet_engine, [controller_name]
+ end
end
end
end
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 4f36b612ae..32fa54a362 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -10,8 +10,17 @@ module Rails
class_option :orm, banner: "NAME", type: :string, required: true,
desc: "ORM to generate the controller for"
+ class_option :html, type: :boolean, default: true,
+ desc: "Generate a scaffold with HTML output"
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
+ def handle_skip
+ unless options[:html]
+ @options = @options.merge(template_engine: false, helper: false)
+ end
+ end
+
def create_controller_files
template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb")
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 d6bce40b0c..e11d357314 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -4,13 +4,17 @@ require_dependency "<%= namespaced_file_path %>/application_controller"
<% end -%>
<% module_namespacing do -%>
class <%= controller_class_name %>Controller < ApplicationController
+ before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
+
# GET <%= route_url %>
# GET <%= route_url %>.json
def index
@<%= plural_table_name %> = <%= orm_class.all(class_name) %>
respond_to do |format|
+ <%- if options[:html] -%>
format.html # index.html.erb
+ <%- end -%>
format.json { render json: <%= "@#{plural_table_name}" %> }
end
end
@@ -18,14 +22,15 @@ class <%= controller_class_name %>Controller < ApplicationController
# GET <%= route_url %>/1
# GET <%= route_url %>/1.json
def show
- @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
-
respond_to do |format|
+ <%- if options[:html] -%>
format.html # show.html.erb
+ <%- end -%>
format.json { render json: <%= "@#{singular_table_name}" %> }
end
end
+ <%- if options[:html] -%>
# GET <%= route_url %>/new
# GET <%= route_url %>/new.json
def new
@@ -39,8 +44,8 @@ class <%= controller_class_name %>Controller < ApplicationController
# GET <%= route_url %>/1/edit
def edit
- @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
end
+ <%- end -%>
# POST <%= route_url %>
# POST <%= route_url %>.json
@@ -49,10 +54,14 @@ class <%= controller_class_name %>Controller < ApplicationController
respond_to do |format|
if @<%= orm_instance.save %>
+ <%- if options[:html] -%>
format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> }
+ <%- end -%>
format.json { render json: <%= "@#{singular_table_name}" %>, status: :created, location: <%= "@#{singular_table_name}" %> }
else
+ <%- if options[:html] -%>
format.html { render action: "new" }
+ <%- end -%>
format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
end
end
@@ -61,14 +70,16 @@ class <%= controller_class_name %>Controller < ApplicationController
# PATCH/PUT <%= route_url %>/1
# PATCH/PUT <%= route_url %>/1.json
def update
- @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
-
respond_to do |format|
- if @<%= orm_instance.update_attributes("#{singular_table_name}_params") %>
+ if @<%= orm_instance.update("#{singular_table_name}_params") %>
+ <%- if options[:html] -%>
format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> }
+ <%- end -%>
format.json { head :no_content }
else
+ <%- if options[:html] -%>
format.html { render action: "edit" }
+ <%- end -%>
format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
end
end
@@ -77,24 +88,28 @@ class <%= controller_class_name %>Controller < ApplicationController
# DELETE <%= route_url %>/1
# DELETE <%= route_url %>/1.json
def destroy
- @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
@<%= orm_instance.destroy %>
respond_to do |format|
+ <%- if options[:html] -%>
format.html { redirect_to <%= index_helper %>_url }
+ <%- end -%>
format.json { head :no_content }
end
end
private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_<%= singular_table_name %>
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
+ end
- # Use this method to whitelist the permissible parameters. Example: params.require(:person).permit(:name, :age)
- # Also, you can specialize this method with per-user checking of permissible attributes.
+ # Never trust parameters from the scary internet, only allow the white list through.
def <%= "#{singular_table_name}_params" %>
- <%- if attributes.empty? -%>
+ <%- if attributes_names.empty? -%>
params[<%= ":#{singular_table_name}" %>]
<%- else -%>
- params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes.map {|a| ":#{a.name}" }.join(', ') %>)
+ params.require(<%= ":#{singular_table_name}" %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>)
<%- end -%>
end
end
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 24308dcf6c..85a8914ccc 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -163,8 +163,8 @@ module Rails
# end
# end
def assert_instance_method(method, content)
- assert content =~ /def #{method}(\(.+\))?(.*?)\n end/m, "Expected to have method #{method}"
- yield $2.strip if block_given?
+ assert content =~ /(\s+)def #{method}(\(.+\))?(.*?)\n\1end/m, "Expected to have method #{method}"
+ yield $3.strip if block_given?
end
alias :assert_method :assert_instance_method
diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb
index 2801749ffe..2826a3ffa1 100644
--- a/railties/lib/rails/generators/test_unit/model/model_generator.rb
+++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb
@@ -3,6 +3,9 @@ require 'rails/generators/test_unit'
module TestUnit # :nodoc:
module Generators # :nodoc:
class ModelGenerator < Base # :nodoc:
+
+ RESERVED_YAML_KEYWORDS = %w(y yes n no true false on off null)
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
class_option :fixture, type: :boolean
@@ -19,6 +22,15 @@ module TestUnit # :nodoc:
template 'fixtures.yml', File.join('test/fixtures', class_path, "#{plural_file_name}.yml")
end
end
+
+ private
+ def yaml_key_value(key, value)
+ if RESERVED_YAML_KEYWORDS.include?(key.downcase)
+ "'#{key}': #{value}"
+ else
+ "#{key}: #{value}"
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
index 5c8780aa64..c9d505c84a 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
@@ -3,12 +3,18 @@
<% unless attributes.empty? -%>
one:
<% attributes.each do |attribute| -%>
- <%= attribute.name %>: <%= attribute.default %>
+ <%= yaml_key_value(attribute.column_name, attribute.default) %>
+ <%- if attribute.polymorphic? -%>
+ <%= yaml_key_value("#{attribute.name}_type", attribute.human_name) %>
+ <%- end -%>
<% end -%>
two:
<% attributes.each do |attribute| -%>
- <%= attribute.name %>: <%= attribute.default %>
+ <%= yaml_key_value(attribute.column_name, attribute.default) %>
+ <%- if attribute.polymorphic? -%>
+ <%= yaml_key_value("#{attribute.name}_type", attribute.human_name) %>
+ <%- end -%>
<% end -%>
<% else -%>
# This model initially had no columns defined. If you add columns to the
diff --git a/railties/lib/rails/generators/test_unit/observer/observer_generator.rb b/railties/lib/rails/generators/test_unit/observer/observer_generator.rb
deleted file mode 100644
index 64fe694a8b..0000000000
--- a/railties/lib/rails/generators/test_unit/observer/observer_generator.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'rails/generators/test_unit'
-
-module TestUnit # :nodoc:
- module Generators # :nodoc:
- class ObserverGenerator < Base # :nodoc:
- check_class_collision suffix: "ObserverTest"
-
- def create_test_files
- template 'unit_test.rb', File.join('test/models', class_path, "#{file_name}_observer_test.rb")
- end
- end
- end
-end
diff --git a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb
deleted file mode 100644
index 28aa23626a..0000000000
--- a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'test_helper'
-
-<% module_namespacing do -%>
-class <%= class_name %>ObserverTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
-end
-<% end -%>
diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
index c9af2ca832..30a861f09d 100644
--- a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
+++ b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
@@ -1,2 +1,2 @@
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'active_support'
diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
index 3b4fec2e83..8f3ecaadea 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -18,17 +18,12 @@ module TestUnit # :nodoc:
private
def attributes_hash
- return if accessible_attributes.empty?
+ return if attributes_names.empty?
- accessible_attributes.map do |a|
- name = a.name
+ attributes_names.map do |name|
"#{name}: @#{singular_table_name}.#{name}"
end.sort.join(', ')
end
-
- def accessible_attributes
- attributes.reject(&:reference?)
- end
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index 30e1650555..18bd1ece9d 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -36,7 +36,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
end
test "should update <%= singular_table_name %>" do
- put :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
+ patch :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index e94c6a2030..fa5668a5b5 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -1,13 +1,14 @@
require 'action_dispatch/routing/inspector'
-class Rails::InfoController < ActionController::Base
- self.view_paths = File.join(File.dirname(__FILE__), 'templates')
- layout 'application'
+class Rails::InfoController < ActionController::Base # :nodoc:
+ self.view_paths = File.expand_path('../templates', __FILE__)
+ prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
+ layout -> { request.xhr? ? nil : 'application' }
before_filter :require_local!
def index
- redirect_to '/rails/info/routes'
+ redirect_to action: :routes
end
def properties
@@ -15,8 +16,7 @@ class Rails::InfoController < ActionController::Base
end
def routes
- inspector = ActionDispatch::Routing::RoutesInspector.new
- @info = inspector.format(_routes.routes).join("\n")
+ @routes_inspector = ActionDispatch::Routing::RoutesInspector.new(_routes.routes)
end
protected
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index cfdb15a14e..e52d1a8b90 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -56,8 +56,8 @@ module Rails
end
def add(path, options={})
- with = options[:with] || path
- @root[path] = Path.new(self, path, [with].flatten, options)
+ with = Array(options[:with] || path)
+ @root[path] = Path.new(self, path, with, options)
end
def [](path)
@@ -99,15 +99,14 @@ module Rails
protected
def filter_by(constraint)
- yes = []
- no = []
-
+ all = []
all_paths.each do |path|
- paths = path.existent + path.existent_base_paths
- path.send(constraint) ? yes.concat(paths) : no.concat(paths)
+ if path.send(constraint)
+ paths = path.existent
+ paths -= path.children.map { |p| p.send(constraint) ? [] : p.existent }.flatten
+ all.concat(paths)
+ end
end
-
- all = yes - no
all.uniq!
all
end
@@ -135,7 +134,6 @@ module Rails
keys.delete(@current)
@root.values_at(*keys.sort)
end
- deprecate :children
def first
expanded.first
@@ -212,10 +210,6 @@ module Rails
expanded.select { |d| File.directory?(d) }
end
- def existent_base_paths
- map { |p| File.expand_path(p, @root.path) }.select{ |f| File.exist? f }
- end
-
alias to_a expanded
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 5b454e7f20..9437e9c406 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -172,7 +172,7 @@ module Rails
end
end
- delegate :railtie_name, to: "self.class"
+ delegate :railtie_name, to: :class
def config
@config ||= Railtie::Configuration.new
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 3474b02af4..ac806e8006 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -15,7 +15,7 @@
class SourceAnnotationExtractor
class Annotation < Struct.new(:line, :tag, :text)
def self.directories
- @@directories ||= %w(app config lib script test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',')
+ @@directories ||= %w(app config db lib script test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',')
end
# Returns a representation of the annotation that looks like this:
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index 676b475640..1815c2fdc7 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -2,6 +2,6 @@ desc 'Print out all defined routes in match order, with names. Target specific c
task routes: :environment do
all_routes = Rails.application.routes.routes
require 'action_dispatch/routing/inspector'
- inspector = ActionDispatch::Routing::RoutesInspector.new
- puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n"
+ inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
+ puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER'])
end
diff --git a/railties/lib/rails/templates/layouts/application.html.erb b/railties/lib/rails/templates/layouts/application.html.erb
index 53276d3e7c..7352d48e7b 100644
--- a/railties/lib/rails/templates/layouts/application.html.erb
+++ b/railties/lib/rails/templates/layouts/application.html.erb
@@ -22,6 +22,10 @@
a { color: #000; }
a:visited { color: #666; }
a:hover { color: #fff; background-color:#000; }
+
+ h2 { padding-left: 10px; }
+
+ <%= yield :style %>
</style>
</head>
<body>
diff --git a/railties/lib/rails/templates/rails/info/routes.html.erb b/railties/lib/rails/templates/rails/info/routes.html.erb
index 890f6f5b03..2d8a190986 100644
--- a/railties/lib/rails/templates/rails/info/routes.html.erb
+++ b/railties/lib/rails/templates/rails/info/routes.html.erb
@@ -6,4 +6,4 @@
Routes match in priority from top to bottom
</p>
-<p><pre><%= @info %></pre></p> \ No newline at end of file
+<%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/templates/rails/welcome/index.html.erb
index dd09a96de9..abe705618a 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/templates/rails/welcome/index.html.erb
@@ -173,15 +173,18 @@
</style>
<script>
function about() {
- info = document.getElementById('about-content');
- if (window.XMLHttpRequest)
- { xhr = new XMLHttpRequest(); }
- else
- { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
- xhr.open("GET","rails/info/properties",false);
- xhr.send("");
- info.innerHTML = xhr.responseText;
- info.style.display = 'block'
+ var info = document.getElementById('about-content'),
+ xhr;
+
+ if (info.innerHTML === '') {
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", "rails/info/properties", false);
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.send("");
+ info.innerHTML = xhr.responseText;
+ }
+
+ info.style.display = info.style.display === 'none' ? 'block' : 'none';
}
</script>
</head>
@@ -223,7 +226,8 @@
</li>
<li>
- <h2>Set up a default route and remove <span class="filename">public/index.html</span></h2>
+ <h2>Set up a root route to replace this page</h2>
+ <p>You're seeing this page because you're running in development mode and you haven't set a root route yet.</p>
<p>Routes are set up in <span class="filename">config/routes.rb</span>.</p>
</li>
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index aed7fd4b14..616206dd0b 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -2,7 +2,7 @@
# so fixtures aren't loaded into that environment
abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production?
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'active_support/test_case'
require 'action_controller/test_case'
require 'action_dispatch/testing/integration'
diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb
new file mode 100644
index 0000000000..45b764fa6b
--- /dev/null
+++ b/railties/lib/rails/welcome_controller.rb
@@ -0,0 +1,7 @@
+class Rails::WelcomeController < ActionController::Base # :nodoc:
+ self.view_paths = File.expand_path('../templates', __FILE__)
+ layout nil
+
+ def index
+ end
+end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 2ea1d2aff4..491faf4af9 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -3,11 +3,10 @@ ENV["RAILS_ENV"] ||= "test"
require File.expand_path("../../../load_paths", __FILE__)
require 'stringio'
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'fileutils'
require 'active_support'
-
require 'action_controller'
require 'rails/all'
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index f98915d1cc..638df8ca23 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -45,7 +45,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- get '*path', to: lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] }
+ get '*path', to: lambda { |env| [200, { "Content-Type" => "text/html" }, ["Not an asset"]] }
end
RUBY
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index b9d18f4582..654a44e648 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1,5 +1,6 @@
require "isolation/abstract_unit"
require 'rack/test'
+require 'env_helpers'
class ::MyMailInterceptor
def self.delivering_email(email); email; end
@@ -17,6 +18,7 @@ module ApplicationTests
class ConfigurationTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
include Rack::Test::Methods
+ include EnvHelpers
def new_app
File.expand_path("#{app_path}/../new_app")
@@ -41,6 +43,16 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
+ test "Rails.env does not set the RAILS_ENV environment variable which would leak out into rake tasks" do
+ require "rails"
+
+ switch_env "RAILS_ENV", nil do
+ Rails.env = "development"
+ assert_equal "development", Rails.env
+ assert_nil ENV['RAILS_ENV']
+ end
+ end
+
test "a renders exception on pending migration" do
add_to_config <<-RUBY
config.active_record.migration_error = :page_load
@@ -180,6 +192,16 @@ module ApplicationTests
end
end
+ test "filter_parameters should be able to set via config.filter_parameters in an initializer" do
+ app_file 'config/initializers/filter_parameters_logging.rb', <<-RUBY
+ Rails.application.config.filter_parameters += [ :password, :foo, 'bar' ]
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal [:password, :foo, 'bar'], Rails.application.env_config['action_dispatch.parameter_filter']
+ end
+
test "config.to_prepare is forwarded to ActionDispatch" do
$prepared = false
@@ -395,7 +417,17 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert_equal "Wellington", Rails.application.config.time_zone
+ assert_equal Time.find_zone!("Wellington"), Time.zone_default
+ end
+
+ test "timezone can be set on initializers" do
+ app_file "config/initializers/locale.rb", <<-RUBY
+ Rails.application.config.time_zone = "Central Time (US & Canada)"
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal Time.find_zone!("Central Time (US & Canada)"), Time.zone_default
end
test "raises when an invalid timezone is defined in the config" do
@@ -545,6 +577,54 @@ module ApplicationTests
assert_equal 'permitted', last_response.body
end
+ test "config.action_controller.raise_on_unpermitted_parameters = true" do
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ActionController::Base
+ def create
+ render text: params.require(:post).permit(:name)
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ config.action_controller.raise_on_unpermitted_parameters = true
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ assert_equal true, ActionController::Parameters.raise_on_unpermitted_parameters
+
+ post "/posts", {post: {"title" =>"zomg"}}
+ assert_match "We're sorry, but something went wrong", last_response.body
+ end
+
+ test "config.action_controller.raise_on_unpermitted_parameters is true by default on development" do
+ ENV["RAILS_ENV"] = "development"
+
+ require "#{app_path}/config/environment"
+
+ assert_equal true, ActionController::Parameters.raise_on_unpermitted_parameters
+ end
+
+ test "config.action_controller.raise_on_unpermitted_parameters is true by defaul on test" do
+ ENV["RAILS_ENV"] = "test"
+
+ require "#{app_path}/config/environment"
+
+ assert_equal true, ActionController::Parameters.raise_on_unpermitted_parameters
+ end
+
+ test "config.action_controller.raise_on_unpermitted_parameters is false by default on production" do
+ ENV["RAILS_ENV"] = "production"
+
+ require "#{app_path}/config/environment"
+
+ assert_equal false, ActionController::Parameters.raise_on_unpermitted_parameters
+ end
+
test "config.action_dispatch.ignore_accept_header" do
make_basic_app do |app|
app.config.action_dispatch.ignore_accept_header = true
@@ -582,27 +662,6 @@ module ApplicationTests
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
-
test "config.session_store with :active_record_store with activerecord-session_store gem" do
begin
make_basic_app do |app|
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index f372afa51c..3cb3643e3a 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -95,21 +95,4 @@ class ConsoleTest < ActiveSupport::TestCase
load_environment(true)
assert value
end
-
- def test_active_record_does_not_panic_when_referencing_an_observed_constant
- add_to_config "config.active_record.observers = :user_observer"
-
- app_file "app/models/user.rb", <<-MODEL
- class User < ActiveRecord::Base
- end
- MODEL
-
- app_file "app/models/user_observer.rb", <<-MODEL
- class UserObserver < ActiveRecord::Observer
- end
- MODEL
-
- load_environment
- assert_nothing_raised { User }
- end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 40d1655c9b..bc794e1602 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -50,22 +50,6 @@ module ApplicationTests
assert_equal "test.rails", ActionMailer::Base.default_url_options[:host]
end
- test "uses the default queue for ActionMailer" do
- require "#{app_path}/config/environment"
- assert_kind_of ActiveSupport::Queue, ActionMailer::Base.queue
- end
-
- test "allows me to configure queue for ActionMailer" do
- app_file "config/environments/development.rb", <<-RUBY
- AppTemplate::Application.configure do
- config.action_mailer.queue = ActiveSupport::TestQueue.new
- end
- RUBY
-
- require "#{app_path}/config/environment"
- assert_kind_of ActiveSupport::TestQueue, ActionMailer::Base.queue
- end
-
test "does not include url helpers as action methods" do
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index fde13eeb94..f0d3438aa4 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -40,7 +40,7 @@ module ApplicationTests
end
assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do
- assert_equal "1.1.1.2", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2")
+ assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2")
end
end
diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb
index 2265d220ab..4029984ce9 100644
--- a/railties/test/application/paths_test.rb
+++ b/railties/test/application/paths_test.rb
@@ -59,8 +59,6 @@ module ApplicationTests
assert eager_load.include?(root("app/controllers"))
assert eager_load.include?(root("app/helpers"))
assert eager_load.include?(root("app/models"))
- assert !eager_load.include?(root("app/views")), "expected to not be in the eager_load_path"
- assert !eager_load.include?(root("app/assets")), "expected to not be in the eager_load_path"
end
test "environments has a glob equal to the current environment" do
@@ -75,18 +73,11 @@ module ApplicationTests
assert_in_load_path "vendor"
assert_not_in_load_path "app", "views"
- assert_not_in_load_path "app", "assets"
assert_not_in_load_path "config"
assert_not_in_load_path "config", "locales"
assert_not_in_load_path "config", "environments"
assert_not_in_load_path "tmp"
assert_not_in_load_path "tmp", "cache"
end
-
- test "deprecated children method" do
- assert_deprecated "children is deprecated and will be removed from Rails 4.1." do
- @paths["app/assets"].children
- end
- end
end
end
diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb
deleted file mode 100644
index 219a35da35..0000000000
--- a/railties/test/application/queue_test.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-require 'isolation/abstract_unit'
-
-module ApplicationTests
- class QueueTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- end
-
- def teardown
- teardown_app
- end
-
- def app_const
- @app_const ||= Class.new(Rails::Application)
- end
-
- test "the queue is a SynchronousQueue in test mode" do
- app("test")
- assert_kind_of ActiveSupport::SynchronousQueue, Rails.application.queue
- assert_kind_of ActiveSupport::SynchronousQueue, Rails.queue
- end
-
- test "the queue is a SynchronousQueue in development mode" do
- app("development")
- assert_kind_of ActiveSupport::SynchronousQueue, Rails.application.queue
- assert_kind_of ActiveSupport::SynchronousQueue, Rails.queue
- end
-
- class ThreadTrackingJob
- def initialize
- @origin = Thread.current.object_id
- end
-
- def run
- @target = Thread.current.object_id
- end
-
- def ran_in_different_thread?
- @origin != @target
- end
-
- def ran?
- @target
- end
- end
-
- test "in development mode, an enqueued job will be processed in the same thread" do
- app("development")
-
- job = ThreadTrackingJob.new
- Rails.queue.push job
- sleep 0.1
-
- assert job.ran?, "Expected job to be run"
- refute job.ran_in_different_thread?, "Expected job to run in the same thread"
- end
-
- test "in test mode, an enqueued job will be processed in the same thread" do
- app("test")
-
- job = ThreadTrackingJob.new
- Rails.queue.push job
- sleep 0.1
-
- assert job.ran?, "Expected job to be run"
- refute job.ran_in_different_thread?, "Expected job to run in the same thread"
- end
-
- test "in production, automatically spawn a queue consumer in a background thread" do
- add_to_env_config "production", <<-RUBY
- config.queue = ActiveSupport::Queue.new
- RUBY
-
- app("production")
-
- assert_nil Rails.application.config.queue_consumer
- assert_kind_of ActiveSupport::ThreadedQueueConsumer, Rails.application.queue_consumer
- assert_equal Rails.logger, Rails.application.queue_consumer.logger
- end
-
- test "attempting to marshal a queue will raise an exception" do
- app("test")
- assert_raises TypeError do
- Marshal.dump Rails.queue
- end
- end
-
- def setup_custom_queue
- add_to_env_config "production", <<-RUBY
- require "my_queue"
- config.queue = MyQueue.new
- RUBY
-
- app_file "lib/my_queue.rb", <<-RUBY
- class MyQueue
- def push(job)
- job.run
- end
- end
- RUBY
-
- app("production")
- end
-
- test "a custom queue implementation can be provided" do
- setup_custom_queue
-
- assert_kind_of MyQueue, Rails.queue
-
- job = Struct.new(:id, :ran) do
- def run
- self.ran = true
- end
- end
-
- job1 = job.new(1)
- Rails.queue.push job1
-
- assert_equal true, job1.ran
- end
-
- test "a custom consumer implementation can be provided" do
- add_to_env_config "production", <<-RUBY
- require "my_queue_consumer"
- config.queue = ActiveSupport::Queue.new
- config.queue_consumer = MyQueueConsumer.new
- RUBY
-
- app_file "lib/my_queue_consumer.rb", <<-RUBY
- class MyQueueConsumer
- attr_reader :started
-
- def start
- @started = true
- end
- end
- RUBY
-
- app("production")
-
- assert_kind_of MyQueueConsumer, Rails.application.queue_consumer
- assert Rails.application.queue_consumer.started
- end
-
- test "default consumer is not used with custom queue implementation" do
- setup_custom_queue
-
- assert_nil Rails.application.queue_consumer
- end
- end
-end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 03798d572a..ccb47663d4 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -55,8 +55,8 @@ module ApplicationTests
def db_migrate_and_status
Dir.chdir(app_path) do
- `rails generate model book title:string`
- `bundle exec rake db:migrate`
+ `rails generate model book title:string;
+ bundle exec rake db:migrate`
output = `bundle exec rake db:migrate:status`
assert_match(/database:\s+\S+#{expected[:database]}/, output)
assert_match(/up\s+\d{14}\s+Create books/, output)
@@ -78,9 +78,8 @@ module ApplicationTests
def db_schema_dump
Dir.chdir(app_path) do
- `rails generate model book title:string`
- `rake db:migrate`
- `rake db:schema:dump`
+ `rails generate model book title:string;
+ rake db:migrate db:schema:dump`
schema_dump = File.read("db/schema.rb")
assert_match(/create_table \"books\"/, schema_dump)
end
@@ -97,9 +96,8 @@ module ApplicationTests
def db_fixtures_load
Dir.chdir(app_path) do
- `rails generate model book title:string`
- `bundle exec rake db:migrate`
- `bundle exec rake db:fixtures:load`
+ `rails generate model book title:string;
+ bundle exec rake db:migrate db:fixtures:load`
assert_match(/#{expected[:database]}/,
ActiveRecord::Base.connection_config[:database])
require "#{app_path}/app/models/book"
@@ -122,13 +120,11 @@ module ApplicationTests
def db_structure_dump_and_load
Dir.chdir(app_path) do
- `rails generate model book title:string`
- `bundle exec rake db:migrate`
- `bundle exec rake db:structure:dump`
+ `rails generate model book title:string;
+ bundle exec rake db:migrate db:structure:dump`
structure_dump = File.read("db/structure.sql")
assert_match(/CREATE TABLE \"books\"/, structure_dump)
- `bundle exec rake db:drop`
- `bundle exec rake db:structure:load`
+ `bundle exec rake db:drop db:structure:load`
assert_match(/#{expected[:database]}/,
ActiveRecord::Base.connection_config[:database])
require "#{app_path}/app/models/book"
@@ -152,10 +148,8 @@ module ApplicationTests
def db_test_load_structure
Dir.chdir(app_path) do
- `rails generate model book title:string`
- `bundle exec rake db:migrate`
- `bundle exec rake db:structure:dump`
- `bundle exec rake db:test:load_structure`
+ `rails generate model book title:string;
+ bundle exec rake db:migrate db:structure:dump db:test:load_structure`
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Base.establish_connection 'test'
require "#{app_path}/app/models/book"
@@ -178,4 +172,4 @@ module ApplicationTests
end
end
end
-end \ No newline at end of file
+end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 0a47fd014c..33c753868c 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -50,9 +50,9 @@ module ApplicationTests
assert_match(/AddEmailToUsers: migrated/, output)
output = `rake db:rollback STEP=2`
- assert_match(/drop_table\("users"\)/, output)
+ assert_match(/drop_table\(:users\)/, output)
assert_match(/CreateUsers: reverted/, output)
- assert_match(/remove_column\("users", :email\)/, output)
+ assert_match(/remove_column\(:users, :email, :string\)/, output)
assert_match(/AddEmailToUsers: reverted/, output)
end
end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
index 7a227098ba..744bb93671 100644
--- a/railties/test/application/rake/notes_test.rb
+++ b/railties/test/application/rake/notes_test.rb
@@ -60,6 +60,7 @@ module ApplicationTests
test 'notes finds notes in default directories' do
app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "db/some_seeds.rb", "# TODO: note in db directory"
app_file "lib/some_file.rb", "# TODO: note in lib directory"
app_file "script/run_something.rb", "# TODO: note in script directory"
app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
@@ -80,12 +81,13 @@ module ApplicationTests
assert_match(/note in app directory/, output)
assert_match(/note in config directory/, output)
+ assert_match(/note in db 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
+ assert_equal 6, lines.size
lines.each do |line_number|
assert_equal 4, line_number.size
@@ -96,6 +98,7 @@ module ApplicationTests
test 'notes finds notes in custom directories' do
app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "db/some_seeds.rb", "# TODO: note in db directory"
app_file "lib/some_file.rb", "# TODO: note in lib directory"
app_file "script/run_something.rb", "# TODO: note in script directory"
app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
@@ -116,13 +119,14 @@ module ApplicationTests
assert_match(/note in app directory/, output)
assert_match(/note in config directory/, output)
+ assert_match(/note in db 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_equal 6, lines.size
+ assert_equal 7, lines.size
lines.each do |line_number|
assert_equal 4, line_number.size
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index c6aea03d8c..a8275a2e76 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -110,7 +110,6 @@ module ApplicationTests
app_name = File.basename(app_path)
app_dir = File.dirname(app_path)
moved_app_name = app_name + '_moved'
- moved_app_path = "#{app_path}/#{moved_app_name}"
Dir.chdir(app_dir) do
# Go from "./app/" to "./app/app_moved"
@@ -196,6 +195,16 @@ module ApplicationTests
assert_no_match(/Errors running/, output)
end
+ def test_scaffold_with_references_columns_tests_pass_by_default
+ output = Dir.chdir(app_path) do
+ `rails generate scaffold LineItems product:references cart:belongs_to;
+ bundle exec rake db:migrate db:test:clone test`
+ end
+
+ assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output)
+ assert_no_match(/Errors running/, output)
+ end
+
def test_db_test_clone_when_using_sql_format
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
@@ -250,28 +259,6 @@ module ApplicationTests
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`
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index ffcdeac7f0..22de640236 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -15,6 +15,12 @@ module ApplicationTests
teardown_app
end
+ test "rails/welcome in development" do
+ app("development")
+ get "/"
+ assert_equal 200, last_response.status
+ end
+
test "rails/info/routes in development" do
app("development")
get "/rails/info/routes"
@@ -27,6 +33,36 @@ module ApplicationTests
assert_equal 200, last_response.status
end
+ test "root takes precedence over internal welcome controller" do
+ app("development")
+
+ get '/'
+ assert_match %r{<h1>Getting started</h1>} , last_response.body
+
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def index
+ render text: "foo"
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ root to: "foo#index"
+ end
+ RUBY
+
+ get '/'
+ assert_equal 'foo', last_response.body
+ end
+
+ test "rails/welcome in production" do
+ app("production")
+ get "/"
+ assert_equal 404, last_response.status
+ end
+
test "rails/info/routes in production" do
app("production")
get "/rails/info/routes"
@@ -241,6 +277,77 @@ module ApplicationTests
end
end
+ test 'routes are added and removed when reloading' do
+ app('development')
+
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def index
+ render text: "foo"
+ end
+ end
+ RUBY
+
+ controller :bar, <<-RUBY
+ class BarController < ApplicationController
+ def index
+ render text: "bar"
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'foo', to: 'foo#index'
+ end
+ RUBY
+
+ get '/foo'
+ assert_equal 'foo', last_response.body
+ assert_equal '/foo', Rails.application.routes.url_helpers.foo_path
+
+ get '/bar'
+ assert_equal 404, last_response.status
+ assert_raises NoMethodError do
+ assert_equal '/bar', Rails.application.routes.url_helpers.bar_path
+ end
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'foo', to: 'foo#index'
+ get 'bar', to: 'bar#index'
+ end
+ RUBY
+
+ Rails.application.reload_routes!
+
+ get '/foo'
+ assert_equal 'foo', last_response.body
+ assert_equal '/foo', Rails.application.routes.url_helpers.foo_path
+
+ get '/bar'
+ assert_equal 'bar', last_response.body
+ assert_equal '/bar', Rails.application.routes.url_helpers.bar_path
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'foo', to: 'foo#index'
+ end
+ RUBY
+
+ Rails.application.reload_routes!
+
+ get '/foo'
+ assert_equal 'foo', last_response.body
+ assert_equal '/foo', Rails.application.routes.url_helpers.foo_path
+
+ get '/bar'
+ assert_equal 404, last_response.status
+ assert_raises NoMethodError do
+ assert_equal '/bar', Rails.application.routes.url_helpers.bar_path
+ end
+ end
+
test 'resource routing with irregular inflection' do
app_file 'config/initializers/inflection.rb', <<-RUBY
ActiveSupport::Inflector.inflections do |inflect|
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index 81ed5873a5..f65b5e2f2d 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -1,8 +1,10 @@
require 'isolation/abstract_unit'
+require 'env_helpers'
module ApplicationTests
class RunnerTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
+ include EnvHelpers
def setup
build_app
@@ -67,5 +69,21 @@ module ApplicationTests
assert_match "true", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.application.config.ran"` }
end
+
+ def test_default_environment
+ assert_match "development", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` }
+ end
+
+ def test_environment_with_rails_env
+ with_rails_env "production" do
+ assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` }
+ end
+ end
+
+ def test_environment_with_rack_env
+ with_rack_env "production" do
+ assert_match "production", Dir.chdir(app_path) { `bundle exec rails runner "puts Rails.env"` }
+ end
+ end
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index f99ea13022..6be4a5fe89 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -1,14 +1,14 @@
require 'abstract_unit'
+require 'env_helpers'
require 'rails/commands/console'
class Rails::ConsoleTest < ActiveSupport::TestCase
+ include EnvHelpers
+
class FakeConsole
def self.start; end
end
- def setup
- end
-
def test_sandbox_option
console = Rails::Console.new(app, parse_arguments(["--sandbox"]))
assert console.sandbox?
@@ -78,7 +78,14 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/\sspecial-production\s/, output)
end
end
-
+
+ def test_default_environment_with_rack_env
+ with_rack_env 'production' do
+ start
+ assert_match(/\sproduction\s/, output)
+ end
+ end
+
def test_e_option
start ['-e', 'special-production']
assert_match(/\sspecial-production\s/, output)
@@ -104,6 +111,12 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
assert_match(/\sdevelopment\s/, output)
end
+ def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
+ Rails::Console.stubs(:available_environments).returns(['dev'])
+ options = Rails::Console.parse_arguments(['dev'])
+ assert_match('dev', options[:environment])
+ end
+
private
attr_reader :output
@@ -126,12 +139,4 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
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 d45bdaabf5..38fe8ca544 100644
--- a/railties/test/commands/dbconsole_test.rb
+++ b/railties/test/commands/dbconsole_test.rb
@@ -45,6 +45,18 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
ENV['RAILS_ENV'] = "test"
end
+ def test_rails_env_is_development_when_argument_is_dev
+ Rails::DBConsole.stubs(:available_environments).returns(['development', 'test'])
+ options = Rails::DBConsole.new.send(:parse_arguments, ['dev'])
+ assert_match('development', options[:environment])
+ end
+
+ def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
+ Rails::DBConsole.stubs(:available_environments).returns(['dev'])
+ options = Rails::DBConsole.new.send(:parse_arguments, ['dev'])
+ assert_match('dev', options[:environment])
+ end
+
def test_mysql
dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], 'db')
start(adapter: 'mysql', database: 'db')
@@ -116,6 +128,14 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase
assert !aborted
end
+ def test_sqlite3_db_without_defined_rails_root
+ Rails.stubs(:respond_to?)
+ Rails.expects(:respond_to?).with(:root).once.returns(false)
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', Rails.root.join('../config/db.sqlite3').to_s)
+ start(adapter: 'sqlite3', database: 'config/db.sqlite3')
+ assert !aborted
+ end
+
def test_oracle
dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user@db')
start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret')
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 4a3ea82e3d..cb57b3c0cd 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -1,7 +1,9 @@
require 'abstract_unit'
+require 'env_helpers'
require 'rails/commands/server'
class Rails::ServerTest < ActiveSupport::TestCase
+ include EnvHelpers
def test_environment_with_server_option
args = ["thin", "-e", "production"]
@@ -23,4 +25,18 @@ class Rails::ServerTest < ActiveSupport::TestCase
assert_nil options[:environment]
assert_equal 'thin', options[:server]
end
+
+ def test_environment_with_rails_env
+ with_rails_env 'production' do
+ server = Rails::Server.new
+ assert_equal 'production', server.options[:environment]
+ end
+ end
+
+ def test_environment_with_rack_env
+ with_rack_env 'production' do
+ server = Rails::Server.new
+ assert_equal 'production', server.options[:environment]
+ end
+ end
end
diff --git a/railties/test/configuration/middleware_stack_proxy_test.rb b/railties/test/configuration/middleware_stack_proxy_test.rb
index 5984c0b425..2442cb995d 100644
--- a/railties/test/configuration/middleware_stack_proxy_test.rb
+++ b/railties/test/configuration/middleware_stack_proxy_test.rb
@@ -1,6 +1,7 @@
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'rails/configuration'
require 'active_support/test_case'
+require 'minitest/mock'
module Rails
module Configuration
diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb
index addf49cdb6..7970913d21 100644
--- a/railties/test/engine_test.rb
+++ b/railties/test/engine_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
class EngineTest < ActiveSupport::TestCase
- it "reports routes as available only if they're actually present" do
+ test "reports routes as available only if they're actually present" do
engine = Class.new(Rails::Engine) do
def initialize(*args)
@routes = nil
diff --git a/railties/test/env_helpers.rb b/railties/test/env_helpers.rb
new file mode 100644
index 0000000000..6223c85bbf
--- /dev/null
+++ b/railties/test/env_helpers.rb
@@ -0,0 +1,26 @@
+module EnvHelpers
+ private
+
+ def with_rails_env(env)
+ switch_env 'RAILS_ENV', env do
+ switch_env 'RACK_ENV', nil do
+ yield
+ end
+ end
+ end
+
+ def with_rack_env(env)
+ switch_env 'RACK_ENV', env do
+ switch_env 'RAILS_ENV', nil do
+ yield
+ end
+ end
+ end
+
+ def switch_env(key, value)
+ old, ENV[key] = ENV[key], value
+ yield
+ ensure
+ ENV[key] = old
+ end
+end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 8af92479c3..54734ed260 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -1,8 +1,11 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
+require 'env_helpers'
class ActionsTest < Rails::Generators::TestCase
include GeneratorsTestHelper
+ include EnvHelpers
+
tests Rails::Generators::AppGenerator
arguments [destination_root]
@@ -154,10 +157,9 @@ class ActionsTest < Rails::Generators::TestCase
def test_rake_should_run_rake_command_with_default_env
generator.expects(:run).once.with("rake log:clear RAILS_ENV=development", verbose: false)
- old_env, ENV['RAILS_ENV'] = ENV["RAILS_ENV"], nil
- action :rake, 'log:clear'
- ensure
- ENV["RAILS_ENV"] = old_env
+ with_rails_env nil do
+ action :rake, 'log:clear'
+ end
end
def test_rake_with_env_option_should_run_rake_command_in_env
@@ -167,26 +169,23 @@ class ActionsTest < Rails::Generators::TestCase
def test_rake_with_rails_env_variable_should_run_rake_command_in_env
generator.expects(:run).once.with('rake log:clear RAILS_ENV=production', verbose: false)
- old_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production"
- action :rake, 'log:clear'
- ensure
- ENV["RAILS_ENV"] = old_env
+ with_rails_env "production" do
+ action :rake, 'log:clear'
+ end
end
def test_env_option_should_win_over_rails_env_variable_when_running_rake
generator.expects(:run).once.with('rake log:clear RAILS_ENV=production', verbose: false)
- old_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "staging"
- action :rake, 'log:clear', env: 'production'
- ensure
- ENV["RAILS_ENV"] = old_env
+ with_rails_env "staging" do
+ action :rake, 'log:clear', env: 'production'
+ end
end
def test_rake_with_sudo_option_should_run_rake_command_with_sudo
generator.expects(:run).once.with("sudo rake log:clear RAILS_ENV=development", verbose: false)
- old_env, ENV['RAILS_ENV'] = ENV["RAILS_ENV"], nil
- action :rake, 'log:clear', sudo: true
- ensure
- ENV["RAILS_ENV"] = old_env
+ with_rails_env nil do
+ action :rake, 'log:clear', sudo: true
+ end
end
def test_capify_should_run_the_capify_command
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 5ea31f2e0f..945cb61bc1 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -11,9 +11,11 @@ DEFAULT_APP_FILES = %w(
app/assets/stylesheets
app/assets/images
app/controllers
+ app/controllers/concerns
app/helpers
app/mailers
app/models
+ app/models/concerns
app/views/layouts
config/environments
config/initializers
@@ -54,8 +56,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application"/
assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/
assert_file "app/assets/stylesheets/application.css"
- assert_file "config/application.rb", /config\.assets\.enabled = true/
- assert_file "public/index.html", /url\("assets\/rails.png"\);/
end
def test_invalid_application_name_raises_an_error
@@ -224,7 +224,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-sprockets"]
assert_file "config/application.rb" do |content|
assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
- assert_no_match(/config\.assets\.enabled = true/, content)
+ assert_match(/config\.assets\.enabled = false/, content)
end
assert_file "Gemfile" do |content|
assert_no_match(/sass-rails/, content)
@@ -238,6 +238,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/config\.assets\.digest = true/, content)
assert_no_match(/config\.assets\.js_compressor = :uglifier/, content)
assert_no_match(/config\.assets\.css_compressor = :sass/, content)
+ assert_no_match(/config\.assets\.version = '1\.0'/, content)
end
assert_file "test/performance/browsing_test.rb"
end
@@ -251,13 +252,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_generator_if_skip_index_html_is_given
- run_generator [destination_root, '--skip-index-html']
- assert_no_file 'public/index.html'
- assert_no_file 'app/assets/images/rails.png'
- assert_file 'app/assets/images/.keep'
- end
-
def test_creation_of_a_test_directory
run_generator
assert_file 'test'
diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb
index 6ab1cd58c7..c48bc20899 100644
--- a/railties/test/generators/generated_attribute_test.rb
+++ b/railties/test/generators/generated_attribute_test.rb
@@ -117,13 +117,13 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic?
end
end
-
+
def test_polymorphic_reference_is_false
%w(foo bar baz).each do |attribute_type|
assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic?
end
end
-
+
def test_blank_type_defaults_to_string_raises_exception
assert_equal :string, create_generated_attribute(nil, 'title').type
assert_equal :string, create_generated_attribute("", 'title').type
@@ -132,6 +132,13 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
def test_handles_index_names_for_references
assert_equal "post", create_generated_attribute('string', 'post').index_name
assert_equal "post_id", create_generated_attribute('references', 'post').index_name
+ assert_equal "post_id", create_generated_attribute('belongs_to', 'post').index_name
assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name
end
+
+ def test_handles_column_names_for_references
+ assert_equal "post", create_generated_attribute('string', 'post').column_name
+ assert_equal "post_id", create_generated_attribute('references', 'post').column_name
+ assert_equal "post_id", create_generated_attribute('belongs_to', 'post').column_name
+ end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 15e5a0b92b..62d9d1f06a 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -28,7 +28,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration]
assert_migration "db/migrate/change_title_body_from_posts.rb", /class #{migration} < ActiveRecord::Migration/
end
-
+
def test_migration_with_invalid_file_name
migration = "add_something:datetime"
assert_raise ActiveRecord::IllegalMigrationNameError do
@@ -41,9 +41,9 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string", "body:text"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :change, content do |up|
- assert_match(/add_column :posts, :title, :string/, up)
- assert_match(/add_column :posts, :body, :text/, up)
+ assert_method :change, content do |change|
+ assert_match(/add_column :posts, :title, :string/, change)
+ assert_match(/add_column :posts, :body, :text/, change)
end
end
end
@@ -53,15 +53,10 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string:index", "body:text"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :up, content do |up|
- assert_match(/remove_column :posts, :title/, up)
- assert_match(/remove_column :posts, :body/, up)
- end
-
- assert_method :down, content do |down|
- assert_match(/add_column :posts, :title, :string/, down)
- assert_match(/add_column :posts, :body, :text/, down)
- assert_match(/add_index :posts, :title/, down)
+ assert_method :change, content do |change|
+ assert_match(/remove_column :posts, :title, :string/, change)
+ assert_match(/remove_column :posts, :body, :text/, change)
+ assert_match(/remove_index :posts, :title/, change)
end
end
end
@@ -71,14 +66,9 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string", "body:text"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :up, content do |up|
- assert_match(/remove_column :posts, :title/, up)
- assert_match(/remove_column :posts, :body/, up)
- end
-
- assert_method :down, content do |down|
- assert_match(/add_column :posts, :title, :string/, down)
- assert_match(/add_column :posts, :body, :text/, down)
+ assert_method :change, content do |change|
+ assert_match(/remove_column :posts, :title, :string/, change)
+ assert_match(/remove_column :posts, :body, :text/, change)
end
end
end
@@ -88,14 +78,9 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :up, content do |up|
- assert_match(/remove_reference :books, :author/, up)
- assert_match(/remove_reference :books, :distributor, polymorphic: true/, up)
- end
-
- assert_method :down, content do |down|
- assert_match(/add_reference :books, :author, index: true/, down)
- assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, down)
+ assert_method :change, content do |change|
+ assert_match(/remove_reference :books, :author, index: true/, change)
+ assert_match(/remove_reference :books, :distributor, polymorphic: true, index: true/, change)
end
end
end
@@ -105,13 +90,13 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :change, content do |up|
- assert_match(/add_column :posts, :title, :string/, up)
- assert_match(/add_column :posts, :body, :text/, up)
- assert_match(/add_column :posts, :user_id, :integer/, up)
+ assert_method :change, content do |change|
+ assert_match(/add_column :posts, :title, :string/, change)
+ assert_match(/add_column :posts, :body, :text/, change)
+ assert_match(/add_column :posts, :user_id, :integer/, change)
+ assert_match(/add_index :posts, :title/, change)
+ assert_match(/add_index :posts, :user_id, unique: true/, change)
end
- assert_match(/add_index :posts, :title/, content)
- assert_match(/add_index :posts, :user_id, unique: true/, content)
end
end
@@ -120,10 +105,10 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string:inex", "content:text", "user_id:integer:unik"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :change, content do |up|
- assert_match(/add_column :books, :title, :string/, up)
- assert_match(/add_column :books, :content, :text/, up)
- assert_match(/add_column :books, :user_id, :integer/, up)
+ assert_method :change, content do |change|
+ assert_match(/add_column :books, :title, :string/, change)
+ assert_match(/add_column :books, :content, :text/, change)
+ assert_match(/add_column :books, :user_id, :integer/, change)
end
assert_no_match(/add_index :books, :title/, content)
assert_no_match(/add_index :books, :user_id/, content)
@@ -135,13 +120,13 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:index", "body:text", "user_uuid:uniq"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :change, content do |up|
- assert_match(/add_column :posts, :title, :string/, up)
- assert_match(/add_column :posts, :body, :text/, up)
- assert_match(/add_column :posts, :user_uuid, :string/, up)
+ assert_method :change, content do |change|
+ assert_match(/add_column :posts, :title, :string/, change)
+ assert_match(/add_column :posts, :body, :text/, change)
+ assert_match(/add_column :posts, :user_uuid, :string/, change)
+ assert_match(/add_index :posts, :title/, change)
+ assert_match(/add_index :posts, :user_uuid, unique: true/, change)
end
- assert_match(/add_index :posts, :title/, content)
- assert_match(/add_index :posts, :user_uuid, unique: true/, content)
end
end
@@ -150,11 +135,11 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{1,2}:index", "discount:decimal{3.4}:uniq"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :change, content do |up|
- assert_match(/add_column :books, :title, :string, limit: 40/, up)
- assert_match(/add_column :books, :content, :string, limit: 255/, up)
- assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, up)
- assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, up)
+ assert_method :change, content do |change|
+ assert_match(/add_column :books, :title, :string, limit: 40/, change)
+ assert_match(/add_column :books, :content, :string, limit: 255/, change)
+ assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, change)
+ assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, change)
end
assert_match(/add_index :books, :title/, content)
assert_match(/add_index :books, :price/, content)
@@ -167,9 +152,9 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :change, content do |up|
- assert_match(/add_reference :books, :author, index: true/, up)
- assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, up)
+ assert_method :change, content do |change|
+ assert_match(/add_reference :books, :author, index: true/, change)
+ assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, change)
end
end
end
@@ -179,10 +164,10 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "artist_id", "musics:uniq"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :change, content do |up|
- assert_match(/create_join_table :artists, :musics/, up)
- assert_match(/# t.index \[:artist_id, :music_id\]/, up)
- assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, up)
+ assert_method :change, content do |change|
+ assert_match(/create_join_table :artists, :musics/, change)
+ assert_match(/# t.index \[:artist_id, :music_id\]/, change)
+ assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change)
end
end
end
@@ -192,12 +177,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string", "content:text"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :up, content do |up|
- assert_match(/^\s*$/, up)
- end
-
- assert_method :down, content do |down|
- assert_match(/^\s*$/, down)
+ assert_method :change, content do |change|
+ assert_match(/^\s*$/, change)
end
end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 0c7ff0ebe7..01ab77ee20 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -264,13 +264,40 @@ class ModelGeneratorTest < Rails::Generators::TestCase
error = capture(:stderr) { run_generator ["Account", "--force"] }
assert_no_match(/Another migration is already named create_accounts/, error)
assert_no_file old_migration
- assert_migration 'db/migrate/create_accounts.rb'
+ assert_migration "db/migrate/create_accounts.rb"
end
def test_invokes_default_test_framework
run_generator
assert_file "test/models/account_test.rb", /class AccountTest < ActiveSupport::TestCase/
+
assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/
+ assert_generated_fixture("test/fixtures/accounts.yml",
+ {"one"=>{"name"=>"MyString", "age"=>1}, "two"=>{"name"=>"MyString", "age"=>1}})
+ end
+
+ def test_fixtures_use_the_references_ids
+ run_generator ["LineItem", "product:references", "cart:belongs_to"]
+
+ assert_file "test/fixtures/line_items.yml", /product_id: \n cart_id: /
+ assert_generated_fixture("test/fixtures/line_items.yml",
+ {"one"=>{"product_id"=>nil, "cart_id"=>nil}, "two"=>{"product_id"=>nil, "cart_id"=>nil}})
+ end
+
+ def test_fixtures_use_the_references_ids_and_type
+ run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"]
+
+ assert_file "test/fixtures/line_items.yml", /product_id: \n product_type: Product\n cart_id: /
+ assert_generated_fixture("test/fixtures/line_items.yml",
+ {"one"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil},
+ "two"=>{"product_id"=>nil, "product_type"=>"Product", "cart_id"=>nil}})
+ end
+
+ def test_fixtures_respect_reserved_yml_keywords
+ run_generator ["LineItem", "no:integer", "Off:boolean", "ON:boolean"]
+
+ assert_generated_fixture("test/fixtures/line_items.yml",
+ {"one"=>{"no"=>1, "Off"=>false, "ON"=>false}, "two"=>{"no"=>1, "Off"=>false, "ON"=>false}})
end
def test_fixture_is_skipped
@@ -328,4 +355,10 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ private
+ def assert_generated_fixture(path, parsed_contents)
+ fixture_file = File.new File.expand_path(path, destination_root)
+ assert_equal(parsed_contents, YAML.load(fixture_file))
+ end
end
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 9e7626647e..a4d8b3d1b0 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -1,18 +1,19 @@
require 'generators/generators_test_helper'
require 'rails/generators/rails/controller/controller_generator'
require 'rails/generators/rails/model/model_generator'
-require 'rails/generators/rails/observer/observer_generator'
require 'rails/generators/mailer/mailer_generator'
require 'rails/generators/rails/scaffold/scaffold_generator'
class NamespacedGeneratorTestCase < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+
def setup
+ super
Rails::Generators.namespace = TestApp
end
end
class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
- include GeneratorsTestHelper
arguments %w(Account foo bar)
tests Rails::Generators::ControllerGenerator
@@ -81,7 +82,6 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
end
class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
- include GeneratorsTestHelper
arguments %w(Account name:string age:integer)
tests Rails::Generators::ModelGenerator
@@ -141,29 +141,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
end
end
-class NamespacedObserverGeneratorTest < NamespacedGeneratorTestCase
- include GeneratorsTestHelper
- arguments %w(account)
- tests Rails::Generators::ObserverGenerator
-
- def test_invokes_default_orm
- run_generator
- assert_file "app/models/test_app/account_observer.rb", /module TestApp/, / class AccountObserver < ActiveRecord::Observer/
- end
-
- def test_invokes_default_orm_with_class_path
- run_generator ["admin/account"]
- assert_file "app/models/test_app/admin/account_observer.rb", /module TestApp/, / class Admin::AccountObserver < ActiveRecord::Observer/
- end
-
- def test_invokes_default_test_framework
- run_generator
- assert_file "test/models/test_app/account_observer_test.rb", /module TestApp/, / class AccountObserverTest < ActiveSupport::TestCase/
- end
-end
-
class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
- include GeneratorsTestHelper
arguments %w(notifier foo bar)
tests Rails::Generators::MailerGenerator
diff --git a/railties/test/generators/observer_generator_test.rb b/railties/test/generators/observer_generator_test.rb
deleted file mode 100644
index 1231827466..0000000000
--- a/railties/test/generators/observer_generator_test.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'generators/generators_test_helper'
-require 'rails/generators/rails/observer/observer_generator'
-
-class ObserverGeneratorTest < Rails::Generators::TestCase
- include GeneratorsTestHelper
- arguments %w(account)
-
- def test_invokes_default_orm
- run_generator
- assert_file "app/models/account_observer.rb", /class AccountObserver < ActiveRecord::Observer/
- end
-
- def test_invokes_default_orm_with_class_path
- run_generator ["admin/account"]
- assert_file "app/models/admin/account_observer.rb", /class Admin::AccountObserver < ActiveRecord::Observer/
- end
-
- def test_invokes_default_test_framework
- run_generator
- assert_file "test/models/account_observer_test.rb", /class AccountObserverTest < ActiveSupport::TestCase/
- end
-
- def test_logs_if_the_test_framework_cannot_be_found
- content = run_generator ["account", "--test-framework=rspec"]
- assert_match(/rspec \[not found\]/, content)
- end
-end
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 8cacca668f..ab00586a64 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -20,17 +20,13 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_match(/@users = User\.all/, m)
end
- assert_instance_method :show, content do |m|
- assert_match(/@user = User\.find\(params\[:id\]\)/, m)
- end
+ assert_instance_method :show, content
assert_instance_method :new, content do |m|
assert_match(/@user = User\.new/, m)
end
- assert_instance_method :edit, content do |m|
- assert_match(/@user = User\.find\(params\[:id\]\)/, m)
- end
+ assert_instance_method :edit, content
assert_instance_method :create, content do |m|
assert_match(/@user = User\.new\(user_params\)/, m)
@@ -39,21 +35,50 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
end
assert_instance_method :update, content do |m|
- assert_match(/@user = User\.find\(params\[:id\]\)/, m)
- assert_match(/@user\.update_attributes\(user_params\)/, m)
+ assert_match(/@user\.update\(user_params\)/, m)
assert_match(/@user\.errors/, m)
end
assert_instance_method :destroy, content do |m|
- assert_match(/@user = User\.find\(params\[:id\]\)/, m)
assert_match(/@user\.destroy/, m)
end
+ assert_instance_method :set_user, content do |m|
+ assert_match(/@user = User\.find\(params\[:id\]\)/, m)
+ end
+
assert_match(/def user_params/, content)
assert_match(/params\.require\(:user\)\.permit\(:name, :age\)/, content)
end
end
+ def test_dont_use_require_or_permit_if_there_are_no_attributes
+ run_generator ["User"]
+
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_match(/def user_params/, content)
+ assert_match(/params\[:user\]/, content)
+ end
+ end
+
+ def test_controller_permit_references_attributes
+ run_generator ["LineItem", "product:references", "cart:belongs_to"]
+
+ assert_file "app/controllers/line_items_controller.rb" do |content|
+ assert_match(/def line_item_params/, content)
+ assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :cart_id\)/, content)
+ end
+ end
+
+ def test_controller_permit_polymorphic_references_attributes
+ run_generator ["LineItem", "product:references{polymorphic}"]
+
+ assert_file "app/controllers/line_items_controller.rb" do |content|
+ assert_match(/def line_item_params/, content)
+ assert_match(/params\.require\(:line_item\)\.permit\(:product_id, :product_type\)/, content)
+ end
+ end
+
def test_helper_are_invoked_with_a_pluralized_name
run_generator
assert_file "app/helpers/users_helper.rb", /module UsersHelper/
@@ -70,13 +95,13 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
end
def test_functional_tests
- run_generator
+ run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}"]
assert_file "test/controllers/users_controller_test.rb" do |content|
assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
- assert_match(/post :create, user: \{ age: @user.age, name: @user.name \}/, content)
- assert_match(/put :update, id: @user, user: \{ age: @user.age, name: @user.name \}/, content)
+ assert_match(/post :create, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content)
+ assert_match(/patch :update, id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \}/, content)
end
end
@@ -87,7 +112,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
assert_match(/post :create, user: \{ \}/, content)
- assert_match(/put :update, id: @user, user: \{ \}/, content)
+ assert_match(/patch :update, id: @user, user: \{ \}/, content)
end
end
@@ -102,6 +127,18 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/views/layouts/users.html.erb"
end
+ def test_skip_html_if_required
+ run_generator [ "User", "name:string", "age:integer", "--no-html" ]
+ assert_no_file "app/helpers/users_helper.rb"
+ assert_no_file "app/views/users"
+
+ assert_file "app/controllers/users_controller.rb" do |content|
+ assert_no_match(/format\.html/, content)
+ assert_no_match(/def edit/, content)
+ assert_no_match(/def new/, content)
+ end
+ end
+
def test_default_orm_is_used
run_generator ["User", "--orm=unknown"]
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 54d5a9db6f..431b23b014 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -30,17 +30,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/@product_lines = ProductLine\.all/, m)
end
- assert_instance_method :show, content do |m|
- assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m)
- end
+ assert_instance_method :show, content
assert_instance_method :new, content do |m|
assert_match(/@product_line = ProductLine\.new/, m)
end
- assert_instance_method :edit, content do |m|
- assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m)
- end
+ assert_instance_method :edit, content
assert_instance_method :create, content do |m|
assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m)
@@ -49,21 +45,23 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_instance_method :update, content do |m|
- assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m)
- assert_match(/@product_line\.update_attributes\(product_line_params\)/, m)
+ assert_match(/@product_line\.update\(product_line_params\)/, m)
assert_match(/@product_line\.errors/, m)
end
assert_instance_method :destroy, content do |m|
- assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m)
assert_match(/@product_line\.destroy/, m)
end
+
+ assert_instance_method :set_product_line, content do |m|
+ assert_match(/@product_line = ProductLine\.find\(params\[:id\]\)/, m)
+ end
end
assert_file "test/controllers/product_lines_controller_test.rb" do |test|
assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test)
- assert_match(/post :create, product_line: \{ title: @product_line.title \}/, test)
- assert_match(/put :update, id: @product_line, product_line: \{ title: @product_line.title \}/, test)
+ assert_match(/post :create, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test)
+ assert_match(/patch :update, id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \}/, test)
end
# Views
@@ -89,7 +87,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
assert_match(/post :create, product_line: \{ \}/, content)
- assert_match(/put :update, id: @product_line, product_line: \{ \}/, content)
+ assert_match(/patch :update, id: @product_line, product_line: \{ \}/, content)
end
end
@@ -149,17 +147,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/@admin_roles = Admin::Role\.all/, m)
end
- assert_instance_method :show, content do |m|
- assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m)
- end
+ assert_instance_method :show, content
assert_instance_method :new, content do |m|
assert_match(/@admin_role = Admin::Role\.new/, m)
end
- assert_instance_method :edit, content do |m|
- assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m)
- end
+ assert_instance_method :edit, content
assert_instance_method :create, content do |m|
assert_match(/@admin_role = Admin::Role\.new\(admin_role_params\)/, m)
@@ -168,15 +162,17 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_instance_method :update, content do |m|
- assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m)
- assert_match(/@admin_role\.update_attributes\(admin_role_params\)/, m)
+ assert_match(/@admin_role\.update\(admin_role_params\)/, m)
assert_match(/@admin_role\.errors/, m)
end
assert_instance_method :destroy, content do |m|
- assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m)
assert_match(/@admin_role\.destroy/, m)
end
+
+ assert_instance_method :set_admin_role, content do |m|
+ assert_match(/@admin_role = Admin::Role\.find\(params\[:id\]\)/, m)
+ end
end
assert_file "test/controllers/admin/roles_controller_test.rb",
@@ -203,7 +199,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
run_generator [ "admin/role" ], :behavior => :revoke
# Model
- assert_file "app/models/admin.rb" # ( should not be remove )
+ assert_file "app/models/admin.rb" # ( should not be remove )
assert_no_file "app/models/admin/role.rb"
assert_no_file "test/models/admin/role_test.rb"
assert_no_file "test/fixtures/admin/roles.yml"
@@ -261,6 +257,11 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_no_file "app/assets/stylesheets/posts.css"
end
+ def test_scaffold_generator_no_html
+ run_generator [ "posts", "--no-html" ]
+ assert_no_file "app/assets/stylesheets/scaffold.css"
+ end
+
def test_scaffold_generator_no_javascripts
run_generator [ "posts", "--no-javascripts" ]
assert_file "app/assets/stylesheets/scaffold.css"
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 1e5a4545a1..e4924c8386 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -48,7 +48,7 @@ module SharedGeneratorTests
def test_options_before_application_name_raises_an_error
content = capture(:stderr){ run_generator(["--pretend", destination_root]) }
- assert_match(/Options should be given after the \w+ name. For details run: rails( plugin)? --help\n/, content)
+ assert_match(/Options should be given after the \w+ name. For details run: rails( plugin new)? --help\n/, content)
end
def test_name_collision_raises_an_error
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 0cb65f8e0d..172a42a549 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -9,7 +9,7 @@
require 'fileutils'
require 'bundler/setup' unless defined?(Bundler)
-require 'minitest/autorun'
+require 'active_support/testing/autorun'
require 'active_support/test_case'
RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 08fcddd4bf..a9b237d0a5 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -50,7 +50,7 @@ class InfoControllerTest < ActionController::TestCase
test "info controller renders with routes" do
get :routes
- assert_select 'pre'
+ assert_response :success
end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index fcbe7b6cda..a4a75fe459 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -568,7 +568,7 @@ YAML
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
class Engine < ::Rails::Engine
- endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, 'hello'] }
+ endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, ['hello']] }
end
end
RUBY